Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: PHP: Linkinlyhentäjä

Sivun loppuun

ApE!0 [01.01.2008 21:54:20]

#

Hyvin yksinkertainen, pitkien linkkien lyhentämiseen tarkoitettu koodi, johon
idean sain, kun ajattelin, että miten TinyURL.com yms. toimivat.

Valitettavasti en löytänyt keinoa saada osoite TinyURLn tapaan kansio muotoon
(ilman kansioiden tekemistä), eikä muoto lyhentaja.php?123 suostunut toimimaan,
joten tähän on nyt tyytyminen. Itse tätä voi tosin muokkailla, miten haluaa.

Koodi vaatii:
- riittävät oikeudet sijaitsemalleen kansiolle
tai
- tiedoston linkit.dat, chmodattuna ~0666

HUOM! Lyhennetyn linkin pituus riippuu tietenkin tämän koodin sijainnista!
http://a.fi/?id=000 on huomattavasti lyhyempi, kuin http://koti.mbnet.fi/tunnus/makkara/lyhentaja.php?id=999

DEMO:
http://atteweb.net/fileet/linkinlyhentaja/
http://atteweb.net/fileet/linkinlyhentaja/index.php?id=1

E1: Ylimääräiset merkit, rivinvaihdot sekä osoitteen alku korjattu, $_SERVER taulukko tutkinnassa, juokseva numerointi tulossa (silmukka hirviö veke).
E2: Juokseva numerointi, tarvittavia merkkejä korjattu, silmukkahirviö tapettu.
E3: Moka 1. rivin luonnissa korjattu, ID alkaen 1, jos IDtä ei annettu => näytetään lomake.

<?php

// Tiedosto, missä data sijaitsee. Chmod ~666 riittänee
$tiedosto = "linkit.dat";

// // // // // // // // // // // // // // // // // //

if(!file_exists($tiedosto)){ // jos tiedostoa ei löydy, yritetään luoda sellainen
    if(!@touch($tiedosto)){ // jos ei voida luoda tiedostoa, näytetään virheilmoitus
        die("<h2>Permission denied!</h2><p>Tiedostoa ei voitu luoda automaattisesti => luo tiedosto manuaalisesti!</p>");
    }
    if(!@chmod($tiedosto, 0666)){ // jos tiedostolle ei voida antaa riittävästi oikeuksia, näytetään virheilmoitus
        die("<h2>Permission denied!</h2><p>Tiedostolle ei voitu antaa oikeuksia automaattisesti => chmodaa tiedosto ~0666!</p>");
    }
    $tt = @fopen($tiedosto, "w");
    if(!$tt){ // jos ei voida tehdä 1. riviä, näytetään virheilmoitus
        die("<h2>Permission denied!</h2><p>Tiedostoon ei voitu kirjoittaa 1. riviä => lisää rivi manuaalisesti!</p>");
    }
    @fwrite($tt, "1|:|http://atteweb.net/|:|\r\n");
    @fclose($tt);
}
if(!isset($_GET['do'])){ //jos ei ole annettu mitään "komentoa", oletetaan kävijän haluavan siirtyä lyhennetylle sivulle
    if(!isset($_GET['id'])){ // mikäli idtä ei ole annettu, oletetaan, että alutaan luoda uusi
        echo "<form action=\"?do=kasittele\" method=\"post\">
        <p>Anna haluamasi sivun url:<br/>
        <input type=\"text\" name=\"url\" /><br/>
        <input type=\"submit\" value=\"  -  -  Lisää  -  -  \" /></p>
        </form>";
    }else{ // muutoin yritetään siirtyä sivulle
        $id = $_GET['id']; // id helpommin käsiteltävään muotoon
        $linkit = @file($tiedosto); // Luetaan data taulukkoon
        if(!is_numeric($id)){ // jos ei ole annettu numeroa, tulostetaan virheilmoitus
            die("<h2>ID virheellinen!</h2><p>Linkin ID oli virheellinen! Luo uusi linkki <a href=\"?\">tästä</a></p>");
        }
        for($i=0; $i<count($linkit); $i++){ // tutkaillaan, löytyisikö oikeaa id numeroa
            $palat = explode("|:|", $linkit[$i]); // jaetaan rivi paloihin
            if($palat[0] == $id){ // jos 1. pala on id...
                header("Location: ".$palat[1]); // ...siirrytään 2. palassa olevaan osoitteeseen
                die();
            }
        }
        die("<h2>ID virheellinen!</h2><p>Linkin ID oli virheellinen! Luo uusi linkki <a href=\"?\">tästä</a></p>");
    }
}elseif($_GET['do'] == "kasittele"){ // mikäli ollaan jo käsittelemässä lomaketta, käsitellään se
    $url = $_POST["url"]; // osoite helpompaan muotoon
    $linkit = @file($tiedosto);
    if(!isset($url)){ // jos on unohdettu antaa url, tulostetaan virheilmoitus
        die("<p>Osoite virheellinen; osoite puuttui !</p>");
    }
    if(substr($url, 0, 7) == "http://"){
        $alku = "http://";
    }elseif(substr($url, 0, 8) == "https://"){
        $alku = "https://";
    }else{ // jos ei ala http:// tai https://
        die("<p>Osoite virheellinen; alusta puuttui http:// tai https:// !</p>");
    }
    $url = str_replace(array("http://", "https://"), "", $url); // poistetaan http:// ja https:// jonot
    //$url = preg_replace("/[^a-z0-9=?._~\/\-]/", "", $url); // poistetaan kaikki turhat merkit
    $url = trim($url); // poistetaan mahdolliset vahingossa näppäillyt välilyönnit alusta ja lopusta
    $url = htmlspecialchars($url); // html pois
    $url = str_replace(array("\r\n", "\n", "\r"), "", $url); // rivinvaihdot pois, ettei tiedosto sekoa
    $url = $alku.$url; // yhdistetään alku ja loppuosa
    for($i=0; $i<count($linkit); $i++){ // tutkaillaan, löytyisikö samaa osoitetta ennalta
        $pala = explode("|:|", $linkit[$i]);
        $taulukko[$i] = $pala[0]; // kerätään idt taulukkoon (ehkä hiaman typerästi toteutettu)
        if($pala[1] == $url){ // jos vastaava url löytyi, ei anneta lisätä sitä toiste
            die("<p>Linkki on jo lisätty aiemmin!<br/>Linkin id tunnus on ".$pala[0]."</p>");
        }
    }
    $id = max($taulukko);
    $rivi =  ++$id."|:|".$url."|:|\r\n"; // muodostetaan tiedostoon tallennettava rivi
    $tt = @fopen($tiedosto, "a"); // avataan tiedosto ...
    @fwrite($tt, $rivi); // ... kirjoitetaan ...
    @fclose($tt); // ... ja suljetaan
    if(!$tt){
        die("<h2>Permission denied!</h2><p>Tiedostoon ei voitu kirjoittaa => chmodaa tiedosto 0666</p></p>");
    }
    $linkki = "http://".$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."?id=".$id; // linkki helpommin käsiteltävään muotoon
    // tulostetaan linkki + koodit *hienossa* taulukossa
    echo "<table>
    <tr>
        <td>Linkin kohde</td> <!-- alkuperäinen kohde -->
        <td><a href=\"$url\">$url</a></td>
    </tr>
    <tr>
        <td>Linkki</td> <!-- normaali linkki -->
        <td><a href=\"$linkki\">$linkki</a><br/>
        <input type=\"text\" readonly=\"readonly\" size=\"70\" name=\"url\" value=\"$linkki\" /></td>
    </tr>
    <tr>
        <td>Html koodi</td> <!-- valmis Html koodi -->
        <td><input type=\"text\" readonly=\"readonly\" size=\"70\" name=\"url\" value=\"&lt;a href=&#34;$linkki&#34;&gt;Lyhennetty linkki&lt;&#47;a&gt;\" /></td>
    </tr>
    <tr>
        <td>BBCode koodi linkille</td> <!-- valmis BBCode koodi -->
        <td><input type=\"text\" readonly=\"readonly\" size=\"70\" name=\"url\" value=\"[url=$linkki]Lyhennetty linkki[/url]\" /></td>
    </tr>
</table>";
}
?>

Tiedostoon linkit.dat muodostuu rivejä tähän tyyliin:

...
137|:|https://www.ohjelmointiputka.net/koodivinkit/25110-php-linkinlyhent%C3%A4j%C3%A4|:|
138|:|http://atteweb.net/|:|
140|:|http://www.domain.net/joku_ihme_kansio/vielä_pidempi_kansion_nimi_onpi_tama/taalla_on_viela_yksi_kansio/toodella_pitka_tiedoston_nimi.xyz|:|
...

Olga [28.01.2008 09:07:36]

#

$_SERVER-taulukon jostain solusta olisi varmaan löytynyt tuo kysymysmerkin jälkeinen rimpsu ja .htaccess-tiedostolla ja mod_rewrite-moduulin avulla olisi onnistunut myös tuo hakemistoversio. Miksi tuo linkin id ei ole ihan vain juokseva numero? Id:n generointi kolmen sisäkkäisen silmukan avulla ei voi olla kovinkaan tehokasta :)

Edit: hups, tulipa viesti moneen kertaan (poistin nyt ylimääräiset). Näytti aina Internal Server Erroria viestin lähetyksen jälkeen ja ajattelin notta selaimen refresh korjaa asian :)

Contraband [28.01.2008 17:25:16]

#

Muutamia huomioita syötteen tarkistuksesta:

1.

<?php
if(!ereg("http://", $url) AND !ereg("https://", $url)){ // jos osoite ei ala http:// tai https://, tulostetaan virheilmoitus
        die("<p>Osoitteesta puuttui http:// tai https:// alku!</p>");
    }
?>

Yllä oleva koodi ei kommentista huolimatta tarkista sitä, alkaako osoite http(s):// -merkkijonolla, vaan vain sen, löytyykö kyseistä merkkijonoa osoitteesta ollenkaan. Näin esimerkiksi osoite 123http:// on ohjelman mielestä täysin validi. Tällä puutteella saattaa olla myös tietoturvaulottuvuus.

2.

<?php
$url = str_replace("|:|", "", $url); // poistetaan |:| merkit
?>

Tämä koodi pyrkii suodattamaan |:| -merkkkijonon pois syötteestä, mutta se on mahdollista ohittaa. Esimerkiksi merkkijono a||:|:|b muuttuu suodatuksen jälkeen muotoon a|:|b.

3. Osoitteesta ei missään vaiheessa suodateta pois rivinvaihtomerkkiä (\n), joten linkkitiedostoa on mahdollista sabotoida lähettämällä monirivisiä osoitteita (tämä on mahdollista syötekentän yksirivisyydestä huolimatta).

ApE!0 [28.01.2008 19:29:00]

#

Niinpäs tietysti... Korjataan...

E2: tietoturvatestaaja: kiitos huomautuksesta sivuillanikin :) Asia korjattu. Pitänee ruveta ajattelemaan hieman realistisemmin, että kaikki eivät olekaan hyvin aikein liikkeellä...

Contraband [28.01.2008 23:07:40]

#

Nyt suodatus meni jo liian aggressiiviseksi: esimeriksi osoite http://www.palvelin.fi/~kayttaja ei toimi.

Myös mm. alaviivojen käyttö kannattaisi sallia.

ApE!0 [29.01.2008 00:31:39]

#

Lisäsin = ? . _ ~ - merkit sallittuihin merkkeihin.

Huono puolihan tuosta juoksevasta numeroinnista on, että muiden syöttämät osoitteet saa helposti selville. Siksi ei tuohon kovin salaisia sivuja kannata syöttääkään :) (tämä oli siis alkuperäinen ajatus idn satunnaisuudelle)

Olga [29.01.2008 09:38:22]

#

ApE!0 kirjoitti:

Huono puolihan tuosta juoksevasta numeroinnista on, että muiden syöttämät osoitteet saa helposti selville. Siksi ei tuohon kovin salaisia sivuja kannata syöttääkään :) (tämä oli siis alkuperäinen ajatus idn satunnaisuudelle)

Ihan totta, mutta niin kauan kun tunnisteena käytetään kokonaislukua, ei niitä muiden linkkejä ole kovinkaan vaikea sieltä kaivella. Toki jos linkkejä on todella vähän, niin sittenhän se menee tietysti arpomiseksi :)

moptim [29.01.2008 18:12:46]

#

Ihan jänskä :)
http://atteweb.net/fileet/linkinlyhentaja/index.php?id=107

Meitzi [29.01.2008 23:55:09]

#

Ns. "kansiomuotoa" ei saa toimimaan ilman serveriasetusten muuttamista. (yksinkertaisin tapa lienee tehdä custom 404 errorsivuja joka ei olekkaan virhesivu?)

Sensijaan lyhentaja.php?123 toimii kun käytät vaikka koodia:

foreach($_GET as $nimi => $arvo ) {
  if ($nimi) {
    echo "saatiin " . $nimi . "=" . $arvo;
  }
}

Jossa $nimi kohdalla sinulla on haluamasi tieto.

ApE!0 [30.01.2008 02:34:57]

#

Liian yksinkertaista...

Löytyy muuten parse_url taulukostakin (https://www.ohjelmointiputka.net/hak/?kieli=PHP­&nimi=parse_url)

T.M. [31.01.2008 17:30:35]

#

ite muuttaisin tuon kokonaisluvun sellaiseen muotoon että se käyttää kaikki mahdollisia sallittuja merkkejä urlissa, jotka eivät sekoile eri maiden käyttäjillä, jolloin linkistä tulee entistä lyhyempi...

$linkit = @file($tiedosto); // Luetaan data taulukkoon

... entä jos linkkejä on miljoona? muisti loppuu PHP:ltä tod näk, tai ainakin tuon tiedoston lataaminen hidastuu... älä koskaan käytä suoraan file() funkkaria tiedostoihin joiden sisältö saattaa nousta yli 200kt kokoiseksi (raja jossa PHP:n tiedostoluku hidastuu radikaalisti).

Oma simppeli ehdotukseni saman systeemin toteutukseen:
kun luodaan uusi linkki, tehdään tiedosto jonka nimi on sen ID, eli esim "425375.txt" ja tuo tiedosto sisältää sen oikean osoitteen jonne uudelleenohjataan, tällöin ei tarvitse kuin yrittää avata se ID niminen tiedosto, eikä PHP:ltä kulu juuri yhtään aikaa mihinkään, tosin tietty se tiedosto vie aina sen 4kt tilaa olipa siellä 1 tai 4096 merkkiä, joten tuo taas on tilasyöppö, mutta ketäpä kiinnostaa nykyään muutama giga hukattua tilaa... + edellisten lisäksi luodaan tiedosto johon kirjoitetaan a-moodilla yksi merkki joka linkin lisäyksen aikana, sitten linkkimäärän saa filesize() funkkarilla selville helposti, eikä tiedoston pitäisi hajota kun käytetään a-moodia.

toinen vinkki:

preg_match("/^[1-9][0-9][0-9]$/", $id)){

->

ja vielä kolmas vinkki: älä muokkaa käyttäjän syöttämää linkkiä, meitä ei kiinnosta onko hän kirjoittanut sen väärin tms... trim() voi vetää surutta, muuta ei tarttekaan.

neljäs vinkki: älä rajaa lukuja noin pienelle alueelle, kolminumeroiseen lukuun mahtuu max 1000 arvoa... 10 merkkinen vois olla ihan ok, mutta tuonkin voi sitten tiivistää käyttämällä muitakin merkkejä kuin numeroita siinä ID:ssä, vaikkapa kolmasosaan alkuperöisestä pituudesta jopa.

+ ketä kiinnostaa onko linkki jo lisätty? tilan säästöä? turhaa optimointia, joka ironisesti tekeekin scriptistä hitaan :)

ApE!0 [31.01.2008 19:33:04]

#

T.M. kirjoitti:

... entä jos linkkejä on miljoona? muisti loppuu PHP:ltä tod näk, tai ainakin tuon tiedoston lataaminen hidastuu... älä koskaan käytä suoraan file() funkkaria tiedostoihin joiden sisältö saattaa nousta yli 200kt kokoiseksi (raja jossa PHP:n tiedostoluku hidastuu radikaalisti).

Miljoona riviä hidastaa toki toimintaa huomattavasti, mutta kuten näet, maksimi on 900 linkkiä. Tätä saa toki itsekkin lisättyä.

T.M. kirjoitti:

Oma simppeli ehdotukseni saman systeemin toteutukseen:
kun luodaan uusi linkki, tehdään tiedosto jonka nimi on sen ID, eli esim "425375.txt" ja tuo tiedosto sisältää sen oikean osoitteen jonne uudelleenohjataan, tällöin ei tarvitse kuin yrittää avata se ID niminen tiedosto, eikä PHP:ltä kulu juuri yhtään aikaa mihinkään, tosin tietty se tiedosto vie aina sen 4kt tilaa olipa siellä 1 tai 4096 merkkiä, joten tuo taas on tilasyöppö, mutta ketäpä kiinnostaa nykyään muutama giga hukattua tilaa... + edellisten lisäksi luodaan tiedosto johon kirjoitetaan a-moodilla yksi merkki joka linkin lisäyksen aikana, sitten linkkimäärän saa filesize() funkkarilla selville helposti, eikä tiedoston pitäisi hajota kun käytetään a-moodia.

Mielenkiintoinen idea. Taidan kokeilla toteutusta ja jos saan jotain järkevää aikaan, se voisi jopa tulla tänne asti.

T.M. kirjoitti:

toinen vinkki:

preg_match("/^[1-9][0-9][0-9]$/", $id)){

->

No miksei. Tämän tosin saisi monella muullakin tavalla tehdyksi.

T.M. kirjoitti:

ja vielä kolmas vinkki: älä muokkaa käyttäjän syöttämää linkkiä, meitä ei kiinnosta onko hän kirjoittanut sen väärin tms... trim() voi vetää surutta, muuta ei tarttekaan.

No eihän tuossa ole mitään muuta turhaa, kun preg_replace(). Htmlää ei voi sallia, sillä se saattaisi tuhota sivun ulkoasun. Rivinvaihtoja ei voi sallia, sillä muuten tiedosto hajoaisi (ei muuta väliä, mutta ei näytä kivalta). Alusta ei saa puuttua http(s)://, sillä muuten (kuten tietoturvatestaaja totesi) muodostuisi suuri turvallisuusriski. Tätä ei voida myöskään ottomaattisesti lisätä, sillä sivu saattaa olla todennettu. Koska nämä funkkarit ajetaan, tiedoston koko saattaa pienetä.

T.M. kirjoitti:

neljäs vinkki: älä rajaa lukuja noin pienelle alueelle, kolminumeroiseen lukuun mahtuu max 1000 arvoa... 10 merkkinen vois olla ihan ok, mutta tuonkin voi sitten tiivistää käyttämällä muitakin merkkejä kuin numeroita siinä ID:ssä, vaikkapa kolmasosaan alkuperöisestä pituudesta jopa.

kuten mainitsin (ainakin oli tarkoitus) tuota idtä saa muokattua helposti itsekkin (siis säädettyä enemmän numeroita). On toki totta, että kirjaimet lyhentäisi idn pituutta huomattavasti.

T.M. kirjoitti:

+ ketä kiinnostaa onko linkki jo lisätty? tilan säästöä? turhaa optimointia, joka ironisesti tekeekin scriptistä hitaan :)

No turha samaa linkkiä on useamman kerran lisätä, kun se lisää tiedsoton kokoa ja pienentää käytettävissä olevien id numeroiden määrää (joka ilmeisesti on sinulle tärkeää).

Wizard [01.02.2008 16:14:07]

#

Tuo TinyURL voisi olla helppo muodostaa jotakuinkin seuraavasti:

1) .htaccess tiedostolla tehdään bootstrap jossa kaikki kyselyt kohdistetaan index.php tiedostoon
2) Otetaan merkkijono ja tallennetaan kantaan
3) Palautetaan uniqid kannasta käyttäjälle
4) Kun tulee URL kysely, haetaan kannasta match ja kirjoitetaan uusi header('Location: http://');

Tämä tietysti edellyttää hieman koodaamista tuohon bootstrappiin koodilla niiltä osin mitkä ovat normaaleja linkkejä palvelun sisällä ja mitkä ovat muodostettuja linkkejä.

Myös 40x ilmoitukset napattaisiin suoraan bootstrap tiedostoon ja niille joko palautuisi kannasta jotain tai jos ei tulosta kannasta, tulostetaan oma custom virheilmoitus.

Ei sen vaikeampaa itse asiassa ja kannalla saadaan kyllä nopeutta ilman suorituskykyongelmia.

-W-

Wizard [01.02.2008 16:14:58]

#

Jaahans...ISP voisi korjailla palvelintaan...

Meitzi [03.02.2008 01:03:09]

#

Ja sinä voisit katella sitä "poista" napin paikkaa?
Mutta eipä toisiaan ois haitaksi vaikka korjais palvelinta.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta