Windows 10 ? Linux !!! … im Namen der informationellen Selbstbestimmung

Manchmal muss man ja mal über den Tellerrand gucken. Für einen Linuxer bedeutet dies einen Blick in Richtung der Hersteller anderer Betriebssysteme – im Besonderen von PC-Betriebssystemen.

Erster Anlass im vorliegenden Fall war, dass eine Bekannte mich fragte, ob sie denn das “kostenfreie” Angebot zum Upgrade ihres PCs auf eine neue Version des Betriebssystems eines bekannten Herstellers wahrnehmen könne und solle. Ich habe ihr geantwortet, dass ich nicht glaube, dass es von kommerziell tätigen Unternehmen irgendetwas kostenfrei gäbe. I.d.R. würde man im Bereich der IT dann mit kommerziell verwertbaren Informationen zu seiner eigenen Person bezahlen. Ansonsten würde ich mich bzgl. des Herstellers nicht kompetent genug fühlen.

Dabei hatte ich gar keine bösen Hintergedanken. Man ist ja von den Großen in der IT-/Web-Branche ja eh’ bereits Einiges gewohnt – und dass wir alle gerade über die sogenannten sozialen Medien dazu beitragen, dass es in wenigen Jahren keine Privatsphäre mehr geben wird, ist unter aufgeklärten Linux-Usern ja auch kaum ein Geheimnis.

Dann kam meine Frau mit ihrem VMware Guest unter Win 7 an und zeigte mir eine Mitteilung, die besagte, dass auch sie zu den Auserwählten gehöre, die ein kostenfreies Upgrade von Win 7 Pro auf Windows 10 Home (!) nutzen könnten. Meine Antwort: kein Bedarf, Win 7 fliegt ja auch in Kürze von unseren virtuellen Systemen runter.

Aber dann meldete sich auch noch einer unserer wichtigeren Kunden … Da wurde ich dann langsam sauer und fing an, eben ein wenig über den Tellerrand ins Internet hinauszuschauen.

Und musste mir die Augen reiben, weil die Verbraucherschützer von Rheinland-Pfalz vor kurzem bereits ein paar nette Kommentare zu dem Thema Windows 10 formuliert hatten, die ich aus dieser Ecke kaum erwartet hätte.

Für jeden, der ein wenig nachlesen will, hier ein paar Links. Jeder mag sich dann selbst seine Meinung bilden:

http://www.computerbase.de/2015-08/windows-10-microsofts-datensammlung-sorgt-fuer-heftige-kritik/
http://www.welt.de/wirtschaft/webwelt/article145054076/Verbraucherzentrale-warnt-vor-Abhoeranlage-Windows-10.html
https://www.verbraucherzentrale-rlp.de/windows-10—Ueberwachung-bis-zum-letzten-klick-1
http://www.zeit.de/digital/2015-08/windows-zehn-verbraucherzentrale-abhoeranlage-datenschutz
http://www.theguardian.com/technology/2015/jul/31/windows-10-microsoft-faces-criticism-over-privacy-default-settings
http://www.computerworld.com/article/2968288/microsoft-windows/windows-10-makes-diagnostic-data-collection-compulsory.html
http://www.telegraph.co.uk/technology/microsoft/windows/11782807/windows-10-privacy.html
http://www.infoworld.com/article/2956715/microsoft-windows/privacy-and-advertising-in-windows-10-both-sides-of-the-story.html
http://www.newsweek.com/windows-10-recording-users-every-move-
358952

http://www.polygon.com/2015/7/31/9075531/windows-10-privacy-how-to
https://www.reddit.com/r/Windows10/comments/3f38ed/ guide_how_to_disable_data_logging_in_w10
http://lifehacker.com/what-windows-10s-privacy-nightmare-settings-actually-1722267229
http://arstechnica.com/information-technology/2015/08/windows-10s-privacy-policy-is-the-new-normal/
http://www.rt.com/usa/311304-new-windows-privacy-issues/
http://www.computerbase.de/2015-08/kommentar-windows-10/
http://www.sueddeutsche.de/digital/windows-vertrauter-spion-1.2594765
http://www.techrepublic.com/article/windows-10-violates-your-privacy-by-default-heres-how-you-can-protect-yourself/
http://www.zdnet.com/article/how-to-secure-windows-10-the-paranoids-guide/
http://betanews.com/2015/07/31/the-real-price-of-windows-10-is-your-privacy/
http://www.france24.com/en/20150804-windows-10-microsoft-privacy-spying-internet-data-collection-backlash
http://www.heise.de/ix/meldung/Windows-10-Gefaehrlicher-Zertifikats-Wirrwarr-2776810.html
http://www.kuketz-blog.de/kommentar-windows-10-datenschutz-geht-anders/

 
Der wichtigste Link ist vermutlich aber folgender zum “MS Privacy Statement” :

 
Und bitte nicht vergessen, bei jedem der im “Privacy Statement” genannten Punkte – besonders aber bzgl. des Punktes “Personal Data We Collect” auf “Learn More” zu klicken! Erst dann offenbart sich die ganze Vielfalt der erfassten Daten …. bis hinunter auf die Ebene von Mail-Texten, Kontakten und PC- wie Internet-Aktivitäten jeder Form. Flankiert wird das Ganze durch passende “Service Agreements”:

 
Hmmm …, das alles gilt also im Falle einer STANDARD-Installation von Windows 10 Home. Das bedeutet glassklar:

“NO PRIVACY by design” unter Win 10. Oder anders formuliert: Der Respekt vor privaten Daten ist bei MS nicht mehr der Standard.

Mit Interesse habe ich ferner zur Kenntnis genommen, dass das Privacy Statement für ganz unterschiedliche MS Produkte (Windows, Skaype, Outlook, Bing, …) – also vermutlich jeweils separat – gilt. Das allein wirft interessante Fragen auf: Konfiguriert man z.B. Win 10 Home nachträglich – soweit das überhaupt möglich ist – für den Schutz privater Daten, so heißt
das womöglich noch lange nicht, dass die Speicherung von Mailtexten auf MS Servern in den USA im Falle der Nutzung von Outlook (oder des aktuellen Mail-Ablegers) verhindert würde.

Das Akzeptieren des Service Statement ist ferner obligatorisch. Das ist deshalb interessant, weil darüber das weitgehende Datensammeln auch bei Abwahl einiger Datentransferoptionen der Win 10 Home Edition für MS abgesichert wird. Siehe:

 
Eine entsprechende – für normale Anwender/Admins nutzbare – Option zur vollständigen Unterbindung diagnostisch relevanter Daten findet sich offenbar nur in der Enterprise Version von Windows 10. In der Windows 10 PRO und Home Versionen sind dagegen wohl risikobehaftete Klimmzüge über die Registry erforderlich:

 
Siehe beim letzten Link auch die vielfältigen Kommentare …

Das Ganze bedeutet im Klartext: im Falle von Firmen-Lizenzen bietet Win 10 offenbar andere, weitergehende Optionen zum Schutz von PC-Nutzungs- und “privaten” Daten an als im Falle des privaten Einzelanwenders, der dafür sein Upgrade aber kostenlos bekommt.

So wird über das Service Statement für den normalen Home Nutzer indirekt relevant, was MS unter “diagnostischen Daten” subsummiert. Es lese jeder selbst unter den obigen Links nach …

Erstes Fazit:
Arme Win 10 Home User … natürlich gibt es unter Win 10 Optionen, um die Datensammelwut auch nach einer Standardinstallation einzugrenzen. Aber durch die Gültigkeit des Privacy Statements über das reine Windows auf andere MS Produkte (Skype, Outlook, Bing, …) hinaus UND über den Zwang zum Akzeptieren des Service Statements entkommt der Win 10 Home User einer weitgehenden Verwertung von Daten zur Interaktion mit dem System und auch privaten Daten nicht vollständig.
Und wer sagt mir eigentlich bei einem Closed Source System, das Daten verschlüsselt in die USA überträgt, ob das Klicken von irgendwelchen Checkboxen auch das bewirkt, was da versprochen wird ….

Hinweise, dass die Deaktivierung bestimmter Datentransferoptionen letztlich wenig nutzt, sind hier beschrieben:
http://thehackernews.com/2015/08/windows-10-privacy-spying.html

Weitere “Features” von Win 10 Home
Ein weiteres “interessantes” Feature des kostenfreien Windows 10 Upgrades ist ferner der dann nachfolgende Zwang zum Update:

 
Hinzu kommt ferner eine fundamentale Richtungsänderung zur Behandlung von sicherheitsrelevanten Passwörtern für Wi-Fi-/WLAN-Systeme, zu denen ein Win 10 Gerät Verbindung aufnimmt. Solche Passwörter werden bei unbedarfter Nutzung von Win 10 Home auch auf MS Server transferiert. Und nicht genug damit: Outlook, Skape, Facebook-Kontakte des Win 10 Home Users werden u.U. in die Lage versetzt, dieses Passwort für das WLAN zu nutzen as

Konatkte Siehe zu diesem speziellen Thema
Die große Passwort-Sammlung … Wi-Fi Sense unter Win 10

Zweites Fazit:
Im Summe zeigt nun also der Marktführer für PC-Betriebsysteme mal wieder als Letzter der Großen im IT-Business – dafür aber umso ausgeprägter -, wohin die Reise geht:

Der Nutzer soll den Zugang zu IT- und Internet-Ressourcen künftig nicht mehr mit Geld, sondern mit der Aufgabe seine Privatsphäre bezahlen. Die Kontrolle über den Update-Status der eingesetzten Closed Source Software wird ihm dabei zusätzlich entzogen – selbst wenn dies, wie in einem der genannten Artikel beschrieben, auch mal zu Endlosschleifen führen kann. Will man eigentlich mit solcher “Zwangs-Technik” für Unmündige leben?

Privatsphäre ist wichtig – auch und gerade im Zeitalter des Internets !
Viele Vertreter unserer Eliten (Unternehmer, Politiker, …) reden von der Verantwortung für die kommenden Generationen. Da geht es oft um Geld, Schulden, Umwelt, Klimakatastrophe, Flüchtlingsströme, Bevökerungswachstum etc. etc.. Dass aber die von uns z.T. mitentwickelten Anwendungen fürs Internet diesen Generationen gerade die letzten Schlupfwinkel fürs “Private” stehlen, kommt vielen – gerade SW-Entwicklern – erst langsam zu Bewusstsein.

Die bittere Wahrheit ist: Es braucht für die Aufgabe der Privatsphäre gar keine Geheimdienste …. die voranschreitende, für den Anwender angeblich “kostenfreie” Verschmelzung der Betriebssysteme mit Cloud- und Internet-Diensten genügt dazu völlig …

Dass mit dieser aktuellen Entwicklung der IT gerade die elementaren Fundamente und Grundpfeiler der Demokratie untergraben werden, wird leider nur von wenigen Aufrechten klar gesagt, die dafür im besten Fall als altmodisch bezeichnet werden. My home is my castle – das wahr einmal …. Dabei hatte ich im bayerischen Sozialkundeuntericht vor nunmehr fast 40 Jahren noch gelernt, dass Freiheit und Demokratie mit dem Respekt vor der Freiheit, der Meinung und eben auch der Privatsphäre anderer Menschen beginnen …. die moderne Übersetzung für IT-Belange heißt: Respekt vor der “informationellen Selbstbestimmung”. Die Erfinder von Win 10, Facebook, etc. haben den wohl völlig verloren …. wenn sie ihn denn jemals hatten …

Ich bin deshalb gerne konservativ und meinetwegen auch altmodisch. Und ich nutze deshalb Linux, seine Sicherheitsfeatures und bei Bedarf Tor zum Browsen …. und die sogenannten Cloud-Segnungen und die sog. “sozialen” Medien der Vernichter von Privatsphäre im Internet können mir weiterhin gestohlen bleiben …… egal von wem und unter welcher Version ….. ob unter Android, iOS oder eben Win 10 ….

Ceterum censeo:
Die Schlacht um ein freies Internet – im Sinne freier Individuen mit geschützter Privatsphäre, die selbst entscheiden, wann und was sie an Informationen freigeben – wird gerade zig millionenfach verloren. Das Internet und seine Dienste müssen
deshalb eigentlich neu erfunden werden und zwar exakt nach dem Grundsatz “privacy by design” – von den elementaren Protokollen bis hin zu komplexen Anwendungen. Lasst uns im Sinne der Bewahrung von Demokratie für die nächsten Generationen also endlich damit anfangen …. Das passende Betriebssystem als Grundlage gibt es dafür Gott sei Dank ja schon ….

Wer nun noch wissen möchte, ob Windows 10 denn neben seiner Datensammelwut auch etwas Vernünftiges, z.B. an SW- und Bedienkomfort auf dem Desktop, anbieten könne, der möge folgenden Artikel eines Linux-Anwenders, der Win 10 tatsächlich mal ausprobiert hat, lesen:

Tja, dazu passt dann abschließend noch die Einschätzung in folgendem Artikel:

 

SFTP mit Key-Authentication auf (gehosteten) Linux-Servern für Web-Entwickler mit unterschiedlichen Privilegien – I

Da ich immer mal wieder SSH- und SFTP-Verbindungen für gehostete LAMP-Test- und LAMP-Produktiv-Server unter Linux (genauer: Opensuse) konfigurieren muss, stelle ich in einer Artikelserie mal ein paar Hinweise zu den durchzuführenden Überlegungen und administrativen Schritten zusammen.

Ich setze im nachfolgend beschriebenen Fall nur das interne SSH-Subsystem für SFTP und Dateitransfers auf den Test-/Produktiv-Server ein. Bzgl. eventuell vorzusehender unterschiedlicher Zugriffsrechte der (S)FTP User genügt aus meiner Sicht die Diskussion eines Beispiels mit 2 User-Gruppen und zwei unterschiedlichen (Domain-) Verzeichnissen auf dem Server.

Ich gehe im Folgenden davon aus, dass der Leser bereits über etwas Erfahrung mit der Einrichtung von SSH auf einem Remote-System besitzt. Wenn nicht: Siehe
Strato-V-Server mit Opensuse 12.3 – IV – SSH Key Authentication
und vorhergehende Artikel. Im vorliegenden, ersten Artikel der aktuellen Serie behandle ich grundlegende SSH-Einstellungen nur relativ kurz und diskutiere sie hauptsächlich unter Sicherheitsaspekten. Ein zweiter Artikel befasst sich dann mit der konkreten User-Einrichtung für die SFTP-Nutzung.

Abgrenzung SFTP-Uploads von SVN-Updates über SSH

Wir lassen im diskutierten Kontext die Möglichkeiten von SVN (in Kombination mit SSH) für das systematische Füllen von Domain-Verzeichnissen des (Web-) Test-Servers außer acht – obwohl viele Entwickler dies aus Bequemlichkeit nutzen.

Nach meiner Ansicht hat die Code-Versionsverwaltung in Entwicklungsphasen logisch nur wenig mit dem gezielten Upload konsolidierter Dateien einer bestimmten SW-Test- oder -Produktiv-Version in entsprechende Domainen eines Web-Test- oder -Produktiv-Servers zu tun. Letzteres – also der Upload – entspricht eher Export-Vorgängen (z.B. unter Eclipse) oder eben gezielten FTP-Transfers. Unter diesen Prämissen wie auch unter Sicherheitsaspekten hat SVN auf einem exponierten Test- oder Produktivsystem eigentlich wenig verloren. SVN ist vielmehr Teil der Service-Landschaft der Entwicklungsumgebung im lokalen Netz und dient dort der laufenden Protokollierung von Versionsständen einzelner SW-Files sowie deren Konsolidierung.

Elementare Ziele, Anforderungen und Voraussetzungen

Gehosteter Server
Der Server habe die Adresse “serv@myhoster.net” und sei gehostet. Der reguläre SSH-Zugang zu diesem Server für Verwaltungszwecke erfolge zunächst über einen (unpriviligierten) User “usu” über den (high) Port “xxxxx”. Über den “usu”-Account und die Shell logt man sich dann bei Bedarf als “root” ein.

Authentifizierung über Private/Public SSH-Keys
Ein sicherheitsrelevanter Aspekt ist im Falle gehosteter Server die Authentifizierung der Entwickler mittels

  • eines privaten SSH-Keys auf dem Client
  • sowie dessen Gegenstück, nämlich eines auf dem Server hinterlegten Public Keys,

anstelle von User IDs und Passwörtern. Ich gehe im ersten Beitrag auf die wichtigsten zugehörigen Anweisungen in der SSHD Konfigurationsdatei ein. Durch Key-Authentication lassen sich Brute Force Angriffe auf den SSH-Zugang eindämmen; als Admin sollte man aber nicht übersehen, dass durch ein solches Verfahren auch die Anforderungen an die sichere Verwahrung der Schlüssel auf den Clients steigen.

Passwortschutz des private Keys auf dem Client – ?
Eine “Private Key”-Datei auf einem Client-System stellt eine potentielle Gefahrenquelle dar:

  • Mobile Clients können gestohlen werden.
  • n

  • Desktops wie mobile SSH/SFTP-Clients können gehackt werden.

Ein Passwortschutz des privaten Keys ist deshalb eine Mindestanforderung an die Sicherheit – wenngleich auch das nicht gegen implantierte Key-Logger schützt. Eine interessante Frage ist deshalb:

Was gilt im Zshg. mit Key-Passwortschutz eigentlich für gängige SFTP-Clients?

Welcher SFTP-Client unterstützt den Umgang mit Private Keys, wenn diese im Sinne einer durchgehenden Sicherheit auf den Client-Systemen passwort-geschützt hinterlegt werden sollen? Ich gehe in einem der nachfolgenden Artikel deshalb kurz auf diese Frage ein.

Kein SSH-Shell-Zugang
Die User – hier Entwickler – sollen das SSH-basierte SFTP nur für File-Transfers nutzen; sie sollen auf dem Server jedoch keinen Shell-Zugang über SSH erhalten. Administrative Aufgaben auf Test- oder Produktiv-Servern remote durchzuführen, bleibt in unserem Szenario ausschließlich Server-Administratoren vorbehalten.

Verzeichnisse, Entwicklergruppen und Rechte
Während die Einrichtung des SFTP-Servers für eine Key-Authentifizierung nur wenig von Vorgehensweisen abweichen dürfte, die bereits an anderer Stelle im Internet beschrieben sind, ist in unserem Beispiel ein zusätzlicher Punkt folgender:

In Web-Entwicklungsprojekten ist es sinnvoll, definierte Gruppenrechte und manchmal auch User-Rechte auf bestimmten SFTP-Verzeichnissen des Servers – die dort in der Regel Web-Domain-Verzeichnisse umfassen – zu erzwingen. Warum?

  • Einerseits will man die User (Entwickler) womöglich in Log-Dateien unterscheiden können. Man wird Ihnen (genauer: ihren UIDs) deshalb in jedem Fall unterschiedliche Authentifizierungs-Keys zuordnen. Ferner sollen die Entwickler ggf. aber auch gruppenübergreifend kollaborieren. Deshalb braucht man abgestimmte Lese- und Schreib-Rechte auf Verzeichnissen für eine (oder mehrere) definierte Gruppe(n). Für die Darstellung besonderer Privilegien können auch user-bezogene Rechte erforderlich werden. Das Schreibrecht wird man auf einem exponierten Web-Server in jedem Fall so gut wie nie “others” zuordnen.
  • Zudem ist zu gewährleisten, dass der Webserverprozess selbst transferierte Directories und Files lesen und im Falle der Bereitstellung von dynamisch geschriebenen Download-Dateien auch beschreiben kann. Der User, unter dem der Webserver-Prozess läuft, muss auch dann wieder Teil einer Gruppe werden, wenn man “others” keine Schreibrechte zuordnen will.
  • Es ist durchaus normal, dass unterschiedliche Privilegien des Zugriffs auf Verzeichnisse in ein und derselben Verzeichnis-Hierarchie für die Mitglieder unterschiedlicher Entwicklergruppen gefordert werden. Der Zugang zu bestimmten Verzeichnissen und Subverzeichnissen soll in unserem Beispiel per SFTP Chroot und durch das Setzen von Zugriffsrechten auf bestimmte Verzeichnisse limitiert werden. Die Entwickler sollen in unserem Beispiel in 2 Gruppen mit unterschiedlichen Zugriffsrechten auf die Verzeichnishierarchie unterteilt werden.

Wir lassen ferner Zugriffe nur auf Web-Domain-verzeichnisse zu; wir erlauben den Entwicklern keinen Zugriff auf private Home-Verzeichnisse.

Zwei Entwickler-Gruppen mit unterschiedlichen Privilegien
Wir diskutieren in unserem Beispiel den abgestuften Zugriff zweier unterschiedlich privilegierter Entwicklergruppen auf Verzeichnisse unterhalb eines Chroot-Jails “/srv/www/htdocs/webs/project”.

/srv/www/htdocs/webs/project
|
— test
|
— adm
|
— rc

Die Subverzeichnisse entsprechen z.B. verschiedenen Web-Domainen oder Entwicklungsprojekten mit jeweils weiteren Sub-Sub-Verzeichnissen. Da unterschiedliche Berechtigungen für die Domain-Verzeichnisse gegeben sein sollen, werden im Beispiel zwei
Usergruppen devgrp1 und devgrp2 eingerichtet.

Zusätzlich wird der Zugriff der Gruppe “devgrp2” auf Inhalte eines weiteren Chroot-Verzeichnis – hier “test” – eingeschränkt. Die Mitglieder der “devgrp1” dürfen dagegen auf alle definierten Domain-Verzeichnisse zugreifen. Sie sind deshalb zugleich sekundäre Mitglieder der “devgrp2”.

Als Beispiel-User wählen wir hier die User “alpha” (Mitglied von “devgrp1” und “devgrp2”) und “beta” (Mitglied von “devgrp2”). Andere Berechtigungsszenarien lassen sich später aus diesem Beispiel zwanglos ableiten.

  • Mitglieder devgrp1: alpha (Zugriff auf alle Verzeichnisse)
  • Mitglieder devgrp2: beta, alpha (Zugriff nur auf Inhalte von “test”)

Grundlegende Konfiguration und Sicherheitsaspekte der SSH-Einrichtung

Wie man einen Opensuse-Server bei einem Hoster wie Strato grundsätzlich für einen SSH-Zugang mit verschobenem Port und Key-Authentifizierung konfigurieren kann, habe ich bereits in früheren Artikeln in diesem Blog besprochen. Siehe etwa:
Strato-V-Server mit Opensuse 12.3 – IV – SSH Key Authentication
und vorhergehende Artikel.

Ich gehe in diesem Artikel entsprechend davon aus, dass es bereits einen elementar eingerichteten SSH-Service gibt und diskutiere nur einige wichtige Optionen der Konfigurationsdatei “/etc/ssh/sshd_config” des einzurichtenden SSHD-Daemons (s.u.).

Ein paar Hinweise und Warnungen:

Je nachdem, was man an Hosting-Paket (Virtual Server etc.) geordert hat, richtet der Provider eine elementares Linux mit SSH-Zugang ein – meistens ausschließlich für den User root. In der Regel wird man das System in einem ersten Schritt upgraden/updaten und danach auch die SSH-Konfiguration remote und schrittweise abändern. Dabei ist Vorsicht angebracht! Man will sich bei Experimenten mit der SSH-Konfiguration ja schließlich nicht selbst vom Systemzugang ausschließen. Bitte beachten Sie die diesbzgl. Hinweise in den oben genannten Artikeln. Unbedingt sollte man sich beim Provider nach Notfall-Maßnahmen im Falle eines nicht mehr möglichen SSH-Zugangs erkundigen. Meist ist das Booten eines Notfall-Systems mit anschließendem Platten- und Dateizugriff möglich …. Die Notfall-Maßnahmen sollte man auch mal ausprobieren …

Im Zshg. mit SSH- und System-Startup-Modifikationen ist dafür zu sorgen bzw. zu prüfen, dass der SSHD-Daemon bei jedem (automatischen) Systemstart aktiviert wird. Gerade bei gehosteten Servern ist dies ein kritischer Punkt. Provider fahren u.U. die gehosteten Server ohne Rückfrage automatisch rauf und runter, wenn Wartungsmaßnahmen dies erfordern. Nach einem solchen Reboot muss natürlich der SSH-Service auch wieder verfügbar sein.

Jemand der SSH selbst auf einem gehosteten Server konfiguriert, sollte sich deshalb auf Opensuse-, Red Hat- und künftig wohl auch Debian-Systemen mit den entsprechenden Optionen des “systemctl”-Kommandos von “systemd” vertraut machen und überprüfen, dass der SSH(D)-Service für den automatischen Start beim Booten aktiviert ist. Unter Opensuse führt

systemctl enable sshd.service

zur Aktivierung. Den aktuellen Service-Zustand – inkl. Aktvierung – prüft man mit:

mylinux:~ # systemctl status sshd.service
sshd.service – OpenSSH Daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled)
Active: active (running) since Sat 2015-08-08 09:16:50 CEST; 3h 9min ago
Process: 1615 ExecStartPre=/usr/sbin/sshd-gen-keys-start (code=exited, status=0/
SUCCESS)
Main PID: 1650 (sshd)
CGroup: /system.slice/sshd.service
└─1650 /usr/sbin/sshd -D
 
Aug 08 09:16:50 mylinux sshd-gen-keys-start[1615]: Checking for missing server keys in /etc/ssh
Aug 08 09:16:50 mylinux sshd[1650]: Server listening on 0.0.0.0 port xxxxx.
Aug 08 09:16:50 mylinux sshd[1650]: Server listening on :: port xxxxx.

Das korrekte Reboot-Verhalten des gehosteten Servers ist nach Systemeingriffen natürlich nicht nur für SSH/SFTP sondern auch andere Serverdienste zu testen.

Ferner gilt:

Bevor man bestimmte User definitiv vom SSH-Zugang ausschließt, sollte man immer die Zugangsmöglichkeit für einen oder mehrere verbleibende User explizit testen. Mindestens ein verbleibender User sollte immer vorhanden sein.

Ähnliches gilt für die Umstellung auf Key Authentication:

Bevor man eine passwort-basierte Authentisierung für alle User abstellt, sollte man zunächst ausschließlich mit userspezifischen Festlegungen arbeiten und für einzelne User die Funktionstüchtigkeit der Key-Authentifizierung testen. Zur Einrichtung userspezifischer SSHD-Konfiguration dienen die “Match”-Anweisung und zugehörige Blöcke am Ende (!) der SSHD-Konfigurationsdatei. Man lese hierzu unbedingt die ssh-man-Seiten.

Auch der nachfolgende Artikel

SFTP mit Key-Authentication auf (gehosteten) Linux-Servern für Web-Entwickler mit unterschiedlichen Privilegien – II

zeigt beispielhaft, wie man userspezifische Anweisungen anlegt und wie diese aussehen können.

SSH-Zugang für root?
Der direkte SSH-Zugang für root sollte auf einem exponierten System i.d.R. nicht zugelassen werden. Er ist auch überhaupt nicht erforderlich. Erlaubt werden soll und muss der SSH-Zugang im Anfangsstadium dagegen für einen bereits erwähnten, unpriviligierten User “usu”.

Ein solcher unpriviligierter User muss auf dem gehosteten Server erstmal eingerichtet werden. Meist existiert auf gehosteten Servern anfänglich nur der User root. Bevor man den SSH-Zugang für root sperrt, muss der Zugang inkl. Shell-Nutzung für den neuen unpriviligierten User explizit getestet werden. Die Zugangsdaten des unpriviligierten Users sind vom Admin geheimzuhalten. Dieser Account bleibt sein Weg ins System – über den “usu”-Account erfolgt dann indirekt über “su -” der Login als root.

Für den User “usu” sei auf unserem Server bereits ein Public/Private-Key-Paar eingerichtet. Siehe hierzu den oben erwähnten Artikel. Wie man das für andere Accounts macht, zeigt beispielhaft auch der zweite Artikel dieser Serie.

Begrenzung des SSH-Zugangs auf bestimmte IP-Adressen?
Eine spezielle Sperre des Zugangs für allgemeine Host-IP-Adressen bzw. eine Zugangserlaubnis für definierte IP-Adressen gestalte ich normalerweise über eine Firewall und nicht über die SSH-Konfiguration. Die Firewall erlaubt dann für vordefinierte IPs den Zugang zu einem definierten High Port xxxxx für SSH und zu einem definierten Port für HTTPS.

Es sei an dieser Stelle aber ausdrücklich auch darauf hingewiesen, dass die weiter unten benutzte Anweisung

AllowUsers uid

Zusätze der Form

AllowUsers uid@ip.add.re.ss

erlaubt. Gerade bei wenigen SSH/SFTP-Usern, die immer von Clients mit fixer IP-Adresse aus zugreifen, ist das eine gute Möglichkeit, die Nutzung des SSH-Services weiter einzugrenzen.

An dieser Stelle sei auch angemerkt, dass man auf Systemen mit mehreren Netzwerk-Interfaces eingrenzen kann, auf welchem Interface der SSHD angeboten werden soll. Dies geht über die IP-Adresse, die dem Interface zugeordnet ist und der Direktive “ListenAddress”. Für mehr Informationen siehe etwa:
http://www.thegeekstuff.com/2011/
05/openssh-options/

Portverschiebung – Obfuscation
Der Nutzen einer Zuordnung des SSH-Services zu einem selbst definierten Port (hoher Portnummer) wird unterschiedlich diskutiert. Ich persönlich ändere den SSH-Port regelmäßig ab, da zumindest meine Erfahrung zeigt, dass die Anzahl bestimmter Angriffsversuche von Skript-Kiddies danach zurückgeht. Nachfolgend steht “xxxxx” für eine 5-stellige High-Port-Nummer.

Ausschnitte aus der SSHD-Konfigurationsdatei “/etc/ssh/sshd_config”

Nachfolgend ein paar Anweisungen aus einer finalen Konfigurationsdatei für den SSHD als Grundlage von SFTP:

Port xxxxx 
# z.B. Port 64311

AllowUsers usu 
PermitRootLogin no
....
....
RSAAuthentication yes
PubkeyAuthentication yes

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile   .ssh/authorized_keys

# To disable tunneled clear text passwords, change to no here!
PasswordAuthentication no
#PermitEmptyPasswords no

# Change to no to disable s/key passwords
ChallengeResponseAuthentication no

# Set this to 'yes' to enable PAM authentication, account processing, 
# and session processing. If this is enabled, PAM authentication will 
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  ....
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes

AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
#X11DisplayOffset 10
#X11UseLocalhost yes
#PrintMotd yes
#PrintLastLog yes
#TCPKeepAlive yes
#UseLogin no
UsePrivilegeSeparation sandbox          # Default for new installations.

# override default of no subsystems
#Subsystem      sftp    /usr/lib/ssh/sftp-server -u 0007
Subsystem       sftp    internal-sftp

# This enables accepting locale enviroment variables LC_* LANG, see sshd_config(5).
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL

MaxSessions 50
MaxAuthTries 20
MaxStartups 20

....
.....

Hinweis zur SFTP-Aktivierung:
Zu beachten ist die Aktivierung des internen SSH SFTP-Subsystems über

Subsystem    sftp    internal-sftp

Das ist zunächst die einzige Stelle, an der SFTP hier überhaupt ins Spiel kommt! Und noch ein Hinweis für für erfahrene Admins: die “-u” Option von internal-sftp wird hier nicht angewendet. Wir kommen darauf im 2-ten Artikel dieser Serie zurück.

Wichtiger Hinweis zum schrittweisen Vorgehen:
Bitte beachten Sie, dass Sie eine solche finale Konfiguration, die den SSH-Port verschiebt sowie root-Zugang und einen passwort-basierten Zugang grundsätzlich ausschließt, im Sinne der obigen Warnungen nur schrittweise etablieren sollten. Sehen Sie hierzu die oben erwähnten anderen Artikel dieses Blogs.

Kommentierung einiger sicherheitsrelevanter Anweisungen in der SSHD-Konfiguration

So absurd sich das anhören mag: Ein grundsätzliches Problem im Zusammenhang mit SFTP ist gerade, dass wir auf dem entsprechenden Server SSH einrichten müssen.

Der Grund für diese Aussage ist: SSH in den Händen gewiefter und böswilliger User stellt ein enormes potentielles Sicherheitsproblem dar. Siehe z.B.:
http://
www.informit.com/articles/article.aspx?p=602977

https://threatpost.com/encrypted-tunnels-enable-users-circumvent-security-controls-060109/72742

Eines der größten Probleme ist, dass User mit Shellzugang SSH dazu benutzen können, um sog. “Reverse Tunnels” zu privaten Systemen durch Firewalls hindurch zu etablieren, und in diesem Zusammenhang sogar eigene Port-Forwarding Tools einsetzen. Die aus meiner Sicht wirksamen Sicherheitsmaßnahmen gegen dieses Gefährdungspotential sind in diesem Zusammenhang:

  1. den Zugang zu SSH auf wenige User einzugrenzen
  2. den Usern, die SSH lediglich als Basis von SFTP oder als Basis von geplanten, verschlüsselten Tunneln zu Applikationen einsetzen dürfen, den Shellzugang zu entziehen.
  3. Massive Strafandrohungen bei Verletzung von Policies, wenn Administratoren Shell-Zugangsrechte erteilt werden müssen.

Welche Einstellungen in der obigen Datei sshd_config betreffen die Sicherheit?

Wir erkennen zunächst das Sperren des root-Users für SSH über die Anweisung “AllowUsers”. Ein irgendwie geglückter SSH Crack beinhaltet dann zumindest nicht unmittelbar das Risiko einer Betätigung mit Root-Rechten.

Ferner sehen wir die Festlegung eines besonderen Ports “xxxxx” für den SSH-Service. Dieser TCP-Port muss natürlich in der Firewall geöffnet werden. Die Verschiebung des SSH-Services vom Standardport hält gewiefte Angreifer, die ausführliche Portscans unternehmen, deshalb nicht lange ab.

Password-Authentifizierung ist trotz PAM im Sinne der Kommentare im Konfigurationsfile abgeschaltet. Wir lassen PAM hier an, um auf PAM-Ebene noch weitere Möglichkeiten zur User-Einschränkung bzw. User-Überwachung zu bekommen. Wer dem nicht traut, kann die Nutzung von PAM aber auch abschalten.

Die Zeilen

RSAAuthentication yes
PubkeyAuthentication yes

sorgen dagegen für die von uns gewünschte Authentifizierung mit asymmetrischen RSA-Schlüsseln.

Unterbinden von Port Forwarding und Gateways für Tunnel
Da wir für SFTP kein TCP-Forwarding über SSH benötigen, schalten wir es aus Sicherheitsgründen ab. Die Nutzung unseres Servers als SSH-Gateway ist aus Sicherheitsgründen völlig unerwünscht. Wir schalten deshalb auch den Parameter GatewayPorts ab. X11 sollen unsere lieben SFTP-User natürlich auch nicht nutzen.

Das Einrichten und Nutzen von problematischen Reverse Tunnels über unseren Server ist damit künftig für unsere Entwickler schon nicht mehr so ohne weiteres möglich. Wir müssen weiter unten dennoch den Shell-Zugang für die definierten SFTP-User “alpha” und “beta” sperren, denn, wie die SSH-man-page so schön ausführt, gilt

Note that disabling TCP forwarding does not improve
security unless users are also denied shell access, as they can
always install their own forwarders.

U.a. netcat, portfwd und socat sind entsprechende Tools. See
https://www.digi77.com/the-art-of-port-forwarding-on-linux/
http://29a.ch/2009/5/10/forwarding-ports-using-netcat
Aber es geht u.U. auch einfacher:
http://clinging-to-reality.blogspot.com.br/2014/06/reverse-ssh-tunnel-if.html

Ein grundsätzlich wichtiger Punkt ist in diesem Kontext immer auch, dass ein umsichtiger Admin dafür sorgt, dass der sshd-Dämon auf dem Server nicht von jedermann gestartet werden kann:

chmod 754 /usr/sbin/sshd

Zur Sicherheit sollte man auch Compiler deaktivieren,
um das Kompilieren eigener Module aus eigenen Source-Quellen zu unterbinden.

Siehe zu einer Diskussion von Gefahren durch SSH und im Besonderen durch SSH Reverse Tunnels etwa:
http://www.techexams.net/forums/off-topic/67117-circumventing-network-security-via-ssh-tunneling.html

Einige Aspekte der Sicherheit werden im Zusammenhang mit verketteten Tunneln auch in folgendem Artikel angesprochen:
https://linux-blog.anracom.com/2013/11/22/ssh-getunnelter-datenbankzugang-fur-gehostete-lamp-server-i/

Sicherheitsanforderungen im Umgang mit privaten Schlüsseln

An dieser Stelle ist der dringende Hinweis angebracht, dass das sichere Verwahren von privaten Schlüsseln auf SSH/SFTP-Clients weiterer Schutzmaßnahmen bedarf. Hinsichtlich der Sicherheit ist immer die gesamte Kette der involvierten Systeme zu betrachten! Ein SSH-Auth-Key auf einem Windows-System in einem privaten oder öffentlichen Netzwerkumfeld ist ohne weitere Schutzmaßnahmen ggf. gefährlicher als eine Authentifizierung mit User IDs und einem generierten Passwort! Ferner:
Es gibt Leute,

  • die die Verwendung ein und desselben Key-Paars für verschiedene Server
  • und die dauerhafte Verwahrung von Keys auf einem System ohne weitere Verschlüsselungskomponenten wie etwa für die Dateisysteme, auf denen die Schlüssel gelagert werden,

für potentiell gefährlich halten. Gerade der zweite Punkt ist im Zusammenhang mit einem oben bereits diskutierten Diebstahl (z.B. eines Laptops) unbedingt zu unterstreichen. Ich meine aber, dass der generellen Absicherung der Client-Systeme im regulären Betriebszustand eine ebenso große Bedeutung zukommt. Auf einem Client-System, das mit Key Loggern und Trojanern kompromittiert wurde, nutzt der Einsatz einer schlüsselbasierten Authentisierung gar nichts.

Es versteht sich aus meiner Sicht von selbst, dass der private Schlüssel zusätzlich durch ein Passwort geschützt sein sollte. Man tut deshalb gut daran, sich nach FTP-Clients umzusehen, die Key-Authentifizierung mittels passwortgeschützten Private Keys unterstützen.

Genug für heute. Der nächste Artikel

SFTP mit Key-Authentication auf (gehosteten) Linux-Servern für Web-Entwickler mit unterschiedlichen Privilegien – II

befasst sich dann mit der konkreten Einrichtung unserer SFTP-User.

Allgemeine Links

http://www.unixlore.net/articles/five-minutes-to-even-more-secure-ssh.html

http://www.thegeekstuff.com/2011/05/openssh-options/

CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – VI

In the previous articles of this series about an Ajax controlled file upload with PHP progress tracking

CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – V
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – IV
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – III
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – II
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – I

we have shown how we can measure and display the progress of a file upload process with a series of Ajax controlled polling jobs and the progress tracking features of PHP > 5.4. At least in our test example this worked perfectly.

However, for practical purposes and especially when our server users deal with large files we must in addition take better care of some limiting PHP parameters on the server. Both a good server admin and a program developer would, of course, try to find out what file sizes are to be expected on a regular basis and adjust the server parameters accordingly. However, you never know what our beloved users may do. What happens if we talked about file sizes of less than 100MB and suddenly a file with 200MB is transferred to the server?

For which limiting PHP parameters on the server may we run into serious trouble?

Due to security considerations the PHP module interaction with incoming data streams is limited by parameters set for the Apache server. The relevant configuration file is e.g. on Opensuse located at

/etc/php5/apache2/php.ini

The most important limits (set in different sections of the file) are:

; Maximum amount of time each script may spend parsing request data. It’s a good
; idea to limit this time on productions servers in order to eliminate unexpectedly
; long running scripts.
; Default Value: -1 (Unlimited)
; Production Value: 60 (60 seconds)
; http://php.net/max-input-time
max_input_time = 200
 
; Maximum size of POST data that PHP will accept.
; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading.
; http://php.net/post-max-size
post_max_size = 200M
 
; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize =150M
 
; Maximum amount of memory a script may consume (128MB)
; http://php.net/memory-limit
memory_limit = 500M

The description of the first parameter above is somewhat unclear. What is meant by the “time spent on parsing request data”? Is this a part of the (also limited) execution time of the PHP target program of our Ajax transaction? Or is this limit imposed on the time required to read incoming POST data and to fill the $_POST array? If the latter were true a small bandwidth could lead to a violation of the “max_input_time” limit …

Regarding the second parameter the question turns up, whether this
limit is imposed on all transferred POST data including the file data?

The third parameter seems to speak for itself. There is a limit for the size of a file that can be transmitted to the server. However, it is not clear how this parameter affects real world scenarios. Does it stop a transfer already before it starts or only when the limit is reached during the transfer?

Regarding the 4th parameter we may suspect that it becomes important already during the handling (reading, parsing) of the incoming POST data. So, how much of memory (RAM) do we need at the server to handle large files during an upload process?

Warning regarding PHP parameter changes for multi-user situation on real world servers

We were and are discussing a privileged situation in this article series: Only one user uploads exactly one big Zip-container file to a server.

In such a situation it is relatively safe to fiddle around with PHP parameters of the central “php.ini” file (or PHP parameter settings in directory specific files; see the last section of this article). However, as an administrator of a server you should always be aware of the consequences of PHP parameter changes, e.g for memory limits, in a multi-user environment.

In addition you must also take into account that our code examples may be extended towards the case that one user may upload multiple files in parallel in one Ajax transaction.

Remarks on “max_input_time” – you can probably ignore it!

If you look up information about “max_input_time” available on the Internet you may experience that some confusion over the implications of this parameter remains. Especially as PHP’s own documentation is a bit contradictory – just compare what is said in the following manual pages:

 
Therefore, I tested a bit with files up to 1 GByte over slow and fast connections to PHP servers on the Internet. I came to the conclusion that the answer in the following “stackoverflow” discussion
http://stackoverflow.com/questions/11387113/php-file-upload-affected-or-not-by-max-input-time
describes the server behavior correctly. This means:

This parameter has no consequences with respect to connection bandwidth and the resulting upload time required for file data: It does not limit the required upload time. Neither does it reduce the amount of allowed maximum execution time of the PHP program triggered at the end of the file transfer process to the server.

“max_input_time” imposes a limit on the time to read/parse the data

  • after they have completely arrived at the server
  • and before the PHP program, which shall work with the data, is started.

This “parsing” time normally is very small and the standard value of 60 secs should be enough under most circumstances. If these findings are true we do not need to care much about this parameter during our file transfer process to the server. A value of 60 secs should work even for large files of 1 GB ore more on modern servers. At least for a server with sufficient resources under average load.

See also:

 
However, I can imagine circumstances on a server with many users under heavy load, for which this parameter nevertheless needs to be adjusted.

What does the PHP documentation say about the parameters “post_max_size”, “upload_max_filesize” and “memory_limit”?

Regarding these parameters we get at least some clear – though disputable – recommendation from the PHP documentation. At

 
we find the following explanation for “post_max_size”:

Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. If memory limit is enabled by your configure script, memory_limit also affects file uploading. Generally speaking, memory_limit should be larger than post_max_size. When an integer is used, the value is measured in bytes. Shorthand notation, as described in this FAQ, may also be used. If the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty. This can be tracked in various ways, e.g. by passing the $_GET variable to the script processing the data, i.e. <form action=”edit.php?processed=1″>, and then checking if $_GET[‘processed’] is set.

Off topic: For those who find the track-recommendation in the last sentence confusing as it refers to $_GET, see e.g.

 
You can add parameters to your URL and these parameters will appear in $_GET, but if you decided to use the POST mechanism for data transfer these URL-parameters are included in the POST data mechanism of HTTP.

The recommendation for memory sizing is misleading in case of file uploads!

Following the recommendation quoted above would lead to the following relation for the PHP setup:

memory_limit > post_max_size > ( upload_max_file_size * number of files uploaded in parallel ).

Regarding the right side: My understanding is that “upload_max_file_size” sets a limit for each individual file during an upload process. See

 
Actually, I find the recommendation for the parameter “memory_limit” very strange. This would mean that somebody who has to deal with an upload file with a size of 2 GByte would have to allow for memory allocation for a single PHP process in the RAM > 2 GByte. Shall we take such a requirement seriously?

My
answer is NO ! But, of course, you should always test yourself ….

To me only the last relation on the right side of the relation chain makes sense during an upload process. Of course PHP needs some RAM and during file uploads also buffering requires sufficient server RAM. But several GByte to control a continuous incoming stream of data which shall be saved as a file into a directory (for temporary files) on the server? No way! I did some tests – e.g. limit the memory to 32 MB and successfully upload a 1 GB file. Therefore, I agree completely with the findings in the following article:

 
See also:

 
So:

Despite you need RAM for buffering during file uploads it is NOT required to use as much physical RAM as the size of the file you want to upload.

However, it may be wise to have as much RAM as possible if you intend to operate on the file as a whole. This may e.g. become important during phases when a PHP program wants to rewrite file data or read them as fast as possible for whatever purpose. A typical example where you may need sufficient memory is image manipulation.

Nevertheless: Regarding the file transfer process to the server itself the quoted recommendation is in my opinion really misleading. And: Do not forget that a high value for “memory_limit” may lead to server problems in a multi-user situation.

“post_max_size” and “upload_max_filesize” as the main limiting PHP parameters for file uploads

So, only the following condition remains:

post_max_size > upload_max_file_size * number of files uploaded in parallel

But this condition should be taken seriously! There are several things that need to be said about these parameters.

  1. A quick test shows: “post_max_size” imposes a limit on all POST data transferred from client – including file data.
  2. Even for situations in which only one file is uploaded I personally would choose “post_max_size” to be several MBs bigger than “upload_max_filesize”. Just to account for overhead.
  3. In case of an upload of multiple files in parallel (i.e. a situation, which we have not studied in this article series) you have to get an idea about the typical size and number of files to be uploaded in parallel. In such a situation you may also want to adjust the parameter

    ; Maximum number of files that can be uploaded via a single request
    max_file_uploads = 20

  4. There may be differences depending on the PHP version of how and when the server reacts to a violation of either of both parameters. For PHP 5.4 it seems that the server does not allow for an upload if either of the parameters is violated by the size of the transferred file(s) – meaning: the upload does not even start. This in turn may lead to different error situations on the server and
    messages issued by the server – depending on which parameter was violated.
  5. From a developer’s perspective it is a bit annoying that the PHP servers reaction to a violation of “upload_max_filesize” is indeed very different from its reaction a violation of “post_max_size”. See below.

Server reactions to violations of “post_max_size” and “upload_max_filesize”

We need to discuss a bit the reactions of a PHP server towards a violation of the named parameters before we can decide how to react within our PHP or Javascript programs in the course of an Ajax transaction.

Server reaction to a violation of “upload_max_filesize”
The Apache/PHP server reacts to a violation of “upload_max_filesize” by a clear message in

$_FILES[‘userfile’][“error”]

where ‘userfile’ corresponds to the “name” attribute of the HTML file input element. A reasonable way how to react to PHP error messages in $_FILES by PHP applications is described in the highest ranked comment of
http://php.net/manual/en/features.file-upload.errors.php
and also here
https://blog.hqcodeshop.fi/archives/185-PHP-large-file-uploads.html

Server reaction to a violation of “post_max_size”
What about a violation of “post_max_size”? We can only react reliably to an error via our PHP target programs if an error number or a clear, structured message is provided. Unfortunately, this is not the case when the sum of uploaded data via POST becomes bigger than “post_max_size”. When the server detects the violation no content at all is made available in $_POST or $_FILES. So, we have no error-message there a PHP program could react to.

However, we can combine

  • a test for emptiness of the superglobals $POST and $_FILES
  • with some HTTP information from the client, which is saved in $_SERVER,

to react properly in our PHP programs. Such a reaction within our Ajax transactions would naturally include

  • the creation of an error code and an error-message
  • and sending both back within the JSON response to the Javascript client for error control.

When we make a POST request to the server a value of the POST content size is provided by the client and available via the variable

$_SERVER[‘CONTENT_LENGTH’].

See:

 
So, for the purpose of error control we will need to add some test code to the “initial” PHP target program “handle_uploaded_init_files.php5” of our Ajax transaction which started the file upload.

Reasonable reactions of our PHP upload and polling programs to a violation of “post_max_size”

Remember that our initial Ajax transaction for upload triggered the server file “handle_uploaded_init_files.php5”. Therefore, we should some additional code that investigates the violation of post_max_size” there. This would probably look similar to:

if (
	isset( $_SERVER['REQUEST_METHOD'] )      &&
        ($_SERVER['REQUEST_METHOD'] === 'POST' ) &&
        isset( $_SERVER['CONTENT_LENGTH'] )      &&
        (
 empty( $_POST ) )
 ) {
	$max_post_size = ini_get('post_max_size');
	$content_length = $_SERVER['CONTENT_LENGTH'] / 1024 / 1024;
	if ($content_length > $max_post_size ) {
		....
		// Our error treatment ....
		$err_code = ....;
		// create an error message and send it to the Ajax client 
		$err_post_size_msg = ".....";
		....
	}

	....
	....
	// transfer the error code and error message to some named element of the JSON object 
	....
	$ajax_response['err_code'] = $err_code;
	$ajax_response['err_msg'] = $err_post_size_msg;
	.....
	$response = json_encode($ajax_response);
	echo $response;
	exit;
}

 
See also:

&nbsP;
Note that we cannot assume a certain timing of the reaction of the main program in comparison to our polling jobs. It may happen that we have already started the polling sequence before the error messages from our first Ajax transaction arrive at the client. Therefore, also our polling jobs “check_progress.php5” should be able to react to empty superglobals $_POST and $_FILES :

if ( ( empty( $_POST ) ) && empty ( $_FILES ) ) {
	// Our error treatment ....
	// create an error message and send it to the Ajax client 
	// refer to messages that may turn up in parallel from the main PHP program
	....
}

 
The different Javascript client methods which receive their respective Ajaj messages should evaluate the error messages and error numbers from the server, display them and, of course, stop the polling loop in case it is still active. As these are trivial programming steps we do not look deeper into them.

Avoid trouble with limiting PHP parameters before starting the file upload

Although we can react to error situations as described above I think it is better to avoid them. Therefore, I suggest to check file size limits before starting any upload process.

In our special situation with just one big Zip-file to upload we can initiate a file size limit check on the server as soon as we choose the file on the client. This means that the Javascript client must be enabled to react to the file selection action and request some information about the parameters “post_max_size” and “upload_max_filesize” from the server. In addition we need a method to compare the server limits with the size of the chosen file.

Looking into
CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress control – II
we see that we had defined a proper Javascript Control Object [CtrlO] for the upload form

<form id="form_upload" name="init_file_form"  action="handle_uploaded_init_files.php5" method="POST" enctype="multipart/form-data" >

 
which – among other things – contains the file selection input tag:

<input type="
file" name="init_file" id="inp_upl_file" >

 
However, we had not assigned any method to the file selection process itself. We are changing this now:

function Ctrl_File_Upl(my_name) {
	
	this.obj_name = "Obj_" + my_name; 
	
	// Controls related to GOC and dispatched object addresses
	this.GOC = GOC;
        this.SO_Tbl_Info = null; 
        this.SO_Msg      = null; 
			
	// ay to keep the selected file handles 
	this.ay_files = new Array(); 
		
	// msg for 1st Ajax phasefor file upload; 
	this.msg1 = ''; 
		
	// Timeout for file transfer process
	// this.timeout = 500000; // internet servers  
	this.timeout = 300000; 
		
	// define selectors (form, divs) 
	this.div_upload_cont_sel 	= "#" + "div_upload_cont";
	this.div_upload_sel 		= "#" + "div_upload";
	this.p_header_upload_sel 	= "#" + "upl_header" + " > span";
			
	this.form_upload_sel 		= "#" + "form_upload";
	this.input_file_sel 		= "#" + "inp_upl_file";
	this.upl_submit_but 		= "#" + "but_submit_upl";

	this.hinp_upl_tbl_num_sel	= "#" + "hinp_upl_tbl_num";			
	this.hinp_upl_tbl_name_sel	= "#" + "hinp_upl_tbl_name";			
	this.hinp_upl_tbl_snr_sel	= "#" + "hinp_upl_tbl_snr";			
	this.hinp_upl_succ_sel 		= "#" + "hinp_upl_succ";			
	this.hinp_upl_run_type_sel 	= "#" + "hinp_upl_run_type";			
	this.hinp_upl_file_name_sel 	= "#" + "hinp_upl_file_name";			
	this.hinp_upl_file_pipe_sel 	= "#" + "hinp_upl_file_pipe";

	// display the number of extracted and processed files 
	this.num_open_files_sel		= '#' + "num_open_files";
	this.num_extracted_files_sel 	= '#' + "num_extracted_files";
	
	// Other objects on the web page - progress area 
	this.trf_msg_cont	= '#' + "trf_msg_cont";
	this.trf_msg		= '#' + "trf_msg";
	this.imp_msg_cont	= '#' + "imp_msg_cont";
	this.imp_msg		= '#' + "imp_msg";
				
	// Status (!) message box (not the right msg box) 
	this.status_div_cont = '#' + "status_div_cont";
	this.id_progr_msg_p  = "#progr_msg"; 
	
	//progress bar 
	this.id_bar = "#bar"; 
			
	// right msg block
	this.span_main_msg	= "span_msg"; 
	
	// variables to control the obligatory check of the file size 
	this.file_size_is_ok = 1; 
			
	// variables for the Ajax response 
	this.upl_file_succ 	= 0; 
	this.upl_file_name 	= ''; 
        
	// File associated variables
	this.file_name      = '';
        this.file_size_js   = 0; 	// file size detected by JS
        this.file_size      = '';	// file size detected by server
        this.allowed_file_size = 0;	// allowed file size for uploads on the server  
        
	// Processing of files     
        this.num_extracted_files = 0;
	this.file_pipeline 	 = 0; 
	this.import_time 	 = 0; 
	this.transfer_time 	 = 0; 
        
	this.name_succ_dir = ''; 
	this.num_open_files = 1;  

	// transfer time measurement 
	this.date_start = 0;   
	this.date_end = 0; 
	this.ajax_transfer_start = 0; 
	this.ajax_transfer_end 	 = 0; 
			
	// database import time measurement 
	this.date_data_import_start = 0; 
	this.date_data_import_end = 0; 
	this.data_import_start = 0; 
	this.data_import_end = 0; 
			
	this.transfer_time	= 0; 
	this.processing_time = 0; 
	this.time_start = 0; 
			
	// Determine URL for the Form 
	this.url = $(this.form_upload_sel).attr('action'); 
	console.log("Form_Upload_file - url = " + this.url);  				

	// Register methods for event handling 
	this.register_form_events(); 
			 
}	
	
// Method to start uploading the file  
// -------------------------------------------------------------------
Ctrl_File_Upl.prototype.register_form_events = function() {
			
	$(this.input_file_sel).
click(
		$.proxy(this, 'select_file') 
	);
			
	$(this.input_file_sel).change(
		$.proxy(this, 'fetch_allowed_file_size') 
	);
	
	$(this.upl_submit_but).click(
		$.proxy(this, 'submit_form') 
	);
				
	$(this.form_upload_sel).submit( 
		$.proxy(this, 'upl_file') 
	); 
					
}; 

 
The reader recognizes that in contrast to the version of the CtrlO “Ctrl_File_Upl” discussed in previous articles of this series we have added some selector IDs for fields of some other web page areas. But the really important change is an extension of the methods for additional events in “Ctrl_File_Upl.prototype.register_form_events()”:

First, we react to a click of the file selection button of the file input field. This only serves the purpose of resetting fields and message areas on the web page. But we react also to the file selection itself by using the change event of the file input field. This triggers a method “fetch_allowed_file_size()” which retrieves the parameter “upload_max_filesize” from the server.

Note:
We assume here that the server admin was clever enough to set post_max_size > upload_max_filesize!
Therefore, we only will perform a file size comparison with the value of “upload_max_filesize”. If you do not trust your server admin just extend the methods and programs presented below by an additional and separate check for file sizes bigger than “post_max_size”. This should be an easy exercise for you.

Now, let us have a look at the new methods of our Javascript CtrlO :

	
// Method to react to a click on the file selection box 
// ---------------------------------------------------- 
Ctrl_File_Upl.prototype.select_file = function (e) {
	// Call method to reset information and message fields 
	// Note: The following method also deactivates the file submit button !  
	this.reset_upl_info(); 
};

	
// Method to check whether file size is too big   
// ---------------------------------------------
// We check whether the file size is too big 
Ctrl_File_Upl.prototype.fetch_allowed_file_size = function (e) {
			
	this.file_size_is_ok = 0; 
			
	// size of the file im MByte determined on the client 
	this.file_size_js = $(this.input_file_sel)[0].files[0].size/1024/1024;
	console.log("actual file size of chosen file = " + this.file_size_js); 	
			
	// Now trigger an Ajaj transaction 
	var ajax_url = "../func/get_allowed_file_size.php5"; 
	var form_data = ''; 
			
	// 03.07.2015: we avoid setup as this would be taken as the standard for subsequent Ajax jobs 
	$.ajax({
                //contentType: "application/x-www-form-urlencoded; charset=ISO-8859-1",
                // context:  Ctrl_Status
                url: ajax_url, 
		context:  this, 
		data: form_data, 
		type: 'POST', 
		dataType: 'json', 
                success: this.response_allowed_file_size, 
                error: this.error_allowed_file_size
        });
};

// Method for Ajaj error handling during file size check transaction 		
// --------------------------------------------------------------	
Ctrl_File_Upl.prototype.error_allowed_file_size = function(jqxhr, error_type) {
			
	// Reset the cursor 
	$('body').css('cursor', 'default' ); 

	// Error handling
	console.log("From Ctrl_File_Upl ::  got Ajax error for fetch_allowed_file_size" );  
	var status = jqxhr.status; 
	var status_txt = jqxhr.statusText; 
	console.log("From Ctrl_File_Upl.prototype.error_allowed_file_size() ::  status = " + status );  
	console.log("From Ctrl_File_Upl.prototype.allowed_file_size ::  status_text = " + status_txt ); 
	console.log("From Ctrl_File_Upl.prototype.allowed_file_size ::  error_type = " + error_type ); 
			
	var 
msg = "<br>Status: " + status + "  Status text: " + status_txt;    
	this.SO_Msg.show_msg(1, msg); 
};

// Method for Ajaj rsponse handling after file size check transaction 		
// --------------------------------------------------------------			
Ctrl_File_Upl.prototype.response_allowed_file_size = function (json_response, success_code, jqxhr) {
	
	// Reset the cursor 
	$('body').css('cursor', 'default' ); 
				
	var new_msg; 
	var status = jqxhr.status; 
	var status_txt = jqxhr.statusText; 
	console.log("response_allowed_fsize: status = " + status + " , status_text = " + status_txt ); 	

	// The allowed file size on the server
	this.allowed_file_size = json_response['allowed_size'];	
	// parseInt required due to possible MB or GB endings on the server 
	this.allowed_file_size = parseInt(this.allowed_file_size); 
	console.log("allowed file size on server = " + this.allowed_file_size); 	

	// size comparison
	// ----------------
	if ( this.file_size_js > this.allowed_file_size ) {
		this.file_size_is_ok = 0; 
		new_msg = $(this.span_main_msg).html();
		if (new_msg == undefined) {
			new_msg = ""; 
		}
		new_msg += "<br><span style=\"color:#A90000;\">File size too big.</span><br>" +
		"The server allows for files with a size ≤ " +  	
		parseFloat(this.allowed_file_size).toFixed(2) + " MB." + "<br>" + 
		"The size of the chosen file is " + parseFloat(this.file_size_js).toFixed(2) + " MB." + "<br><br>" + 
		"<span style=\"color:#A90000;\">Please choose a different file or reduce the contents !</span>" + "<br><br>" + 
		"If you permanently need a bigger file size limit on the server, please contact your administrator"; 
					
		this.SO_Msg.show_msg(0, new_msg); 
				
	// file size within limits 
	// -------------------------
	else {
		this.file_size_is_ok = 1; 
					
		new_msg = $(this.span_main_msg).html();
		if (new_msg == undefined) {
			new_msg = ""; 
		}
		new_msg += "<br><span style=\"color:#007700;\">File size within server limits.</span><br>" +
		"The server allows for files with a size ≤ " +  parseFloat(this.allowed_file_size).toFixed(2) + " MB." + "<br>" + 
		"The size of the chosen file is " + parseFloat(this.file_size_js).toFixed(2) + " MB." + "<br><br>" + 
		"<span style=\"color:#007700;\">Use the "Start Upload" button to start the file upload!</span>"; 
					
		this.SO_Msg.show_msg(0, new_msg); 
					
		// reactivate the submit button 
		// -----------------------------
		$(this.upl_submit_but).on("click", $.proxy(this, 'submit_form') ); 
		$(this.upl_submit_but).css("color", "#990000"); 
	}
};
	
// Method to reset some form and information fields on the web page 
// ---------------------------------------------------------------
// We have to reset some form and message fields
Ctrl_File_Upl.prototype.reset_upl_info = function() {
				
	var msg_progr = ''; 
	$(this.id_progr_msg_p).html('');
				
	var msg_trf = ''; 
	$(this.trf_msg_cont).css('display', 'none');
	$(this.trf_msg).css('color: #666'); 
	$(this.trf_msg).html(msg_trf);
		    	
	var msg_imp = ''; 
	$(this.imp_msg_cont).css('display', 'none'); 
	$(this.imp_msg).css('color: #666'); 
	$(this.imp_msg).html(msg_imp); 
		    	
	// Deactivate the "Start Upload" Button 
	// ------------------------------------
	$(this.upl_submit_but).off("click"); 
	$(this.upl_submit_but).css("color", "#BBB"); 

	// Reset also the main message area  
	// ----------------------------------------------
	this.SO_Msg.show_msg(0, ''); 

};	

 
This is all pretty straightforward and parts of it are already well known of our
previous descriptions for handling the Ajaj interactions with the server by the help of jQuery functionality.

A short description of what happens is:

  • When you click on the button of the file selection input field contents of fields in the message area of our web page and information fields about upload progress are reset as we assume that a new upload will be started.
  • During reset also the form’s submit button to start a file upload via Ajax/Ajaj is disabled. Note that we use jQuery’s “off(‘event’)”-functionality to to this.
  • As soon as the user selects a specific file we trigger a method which determines and saves the size of the chosen file to a variable and starts an Ajax transaction afterwards. This Ajax interaction calls a target PHP program “get_allowed_file_size.php5” in some directory.
  • The JSON-response of the PHP program is handled by the method
    Ctrl_File_Upl.prototype.response_allowed_file_size.

      The main purpose of this method is to make a comparison of the already determined file size with the limit set on the server and issue some warnings or positive messages. If the file size of the chosen file is within the server’s limit we reactivate our “submit” button of the upload form. (Note that we use jQuery’s “on(‘event’)”-functionality to to this.) Otherwise we keep it inactive – until a more suitable file is chosen by the user.

Thus, by very simple means we prevent any unreasonable upload process already before it can be started by the user.

It remains to show an excerpt of the simple PHP target file:

<?php

// start session and output buffer
session_start();
ob_start(); 

$file_size_limit = ini_get("upload_max_filesize");
$ajax_response = array();
$ajax_response['allowed_size'] = $file_size_limit; 

$ajax_response['sys_msg'] .= ob_get_contents();
ob_end_clean();

$response = json_encode($ajax_response);
echo $response;
exit; 

?>

Nothing special to discuss here.

Can we change the limiting parameters during PHP program execution?

No, we can not. But as a developer you may be able to define directory specific settings both for “post_max_size” and “upload_max_filesize” on the server by uploading “.htaccess”-files or “.user.ini”-files to program directories – if this is allowed by the administrator.

The web page php.net/manual/en/ini.core.php shows a column “Changeable” for all important parameters and the respective allowed change mechanisms.
See also:
http://php.net/manual/en/ini.list.php

Different methods of how to change PHP parameters as a user are described here:

 
However, if you are not a developer but a server admin, preventing users from changing PHP ini-paramters may even be more important for you:

 
Enough for today. In the next article of this series

CSV file upload with ZIP containers, jQuery, Ajax and PHP 5.4 progress tracking – VII

we shall have a look at possible problems resulting from timeout limits set for our Ajax transactions.

Responsive fluid multi-column layouts – the DIV order problem – II

In the last article of this series

Responsive fluid multi-column layouts – the DIV order problem – I

I discussed a standard 3 column fluid design. HTML and CSS codes for a simple example were presented. Our solution depended on a certain order of DIV tags in the HTML code. The tag of the DIV container for the main content “div#main_content” got the last tag position and the CSS property “position:relative;” inside our outer container “div#float_cont”, where the container for 2 of the visible outer left and right “columns” were floated. By setting proper margins for the main content container we gained fluidity and at the same time automatic control over the apparent visual heights of all containers.

In this second article we make a first step towards a responsive design. We assume that the reader is already familiar with the definitions of the major DIV elements of our example page. As in the first article we shall identify the DIVs by their CSS ID representation.

We shall need to overcome some obstacles whilst getting responsive. Because this is a Linux blog we do not care if older and not W3C compliant MS IE browsers can reproduce our suggested CSS recipes for a solution. Code examples given below were tested with Firefox for Linux.

Responsiveness based on viewport width intervals

To start with responsiveness we first define some view port width intervals, for which we want to rearrange major (HTML) elements of our test page. We choose the width intervals a bit arbitrarily below. You may adapt them for your needs:

  • Range III: width range beyond 800px and larger
  • Range II: width range between 540px and 800px
  • Range I: width range below 540px

What are our objectives for the page layout in these ranges?

I shall set up some requirements and conditions for which the DIV order problem gets obvious. What I want to show is that when you deal with responsiveness you may have to align your initial standard HTML/CSS layout with what you want to achieve in view port ranges below certain width thresholds. Some people claim that one should always start designing for the narrow view port situation. My point of view is that with fluid layouts you need to consider both ends of your ranges right from the beginning.

Objectives for Range III:

In range III we just use our (or an improved) initial fluid design.

Objectives for Range II:

In range II the left column shall remain where it is. However, we remove the information in the right column to get more space for the main contents. Instead of omitting “div#right” completely we want to move it to the bottom of the page – and change its width at the same time to 100%. The optical and real height of “div#right” shall be determined by its contents. The “div#main_cont”, however, should keep its position, but use all the space being available at the right of the left column to the right edge of the view port. At the same time “div#main_cont” shall determine the optical (not necessarily the css) height of its own and the left column – if it is the longest. Otherwise the contents of the left column shall determine the height of the upper part on the page.

Objectives for Range I:

We set up an even more complicated scenario for range I:
The “div#main_cont” originally positioned in
the middle shall become dominant now and take the whole width offered. In addition we want to move the left side menu (div#left_nav”) from its original position. To make things tricky we want it to move to the bottom of the expanded DIV container “div#main_cont)” and give it a 100% width, too – so that if someone read and scrolled to the bottom of the “div#main_cont” he would see the available link options. The originally right column “div#right” shall move further down below the repositioned “div#left_nav”.

In addition we want to stop a further shrinking of the displayed main DIV container by a minimum width of 200px.

Later on

  • we shall in addition take care of a better treatment of the horizontal menu placed at the top of our test page in range I
  • and offer additional options to deal with the side menu originally placed left with vertical orientation.

Additional assumptions and requirements:

To make things really difficult, we want to achieve the transitions between Range III and Range II as well as between Range II and Range I without changing the HTML code defined for Range III. In addition we want to avoid Javascript/jQuery involvement to change the node order in the HTML tree during transitions. The reordering of the DIV containers defined above shall be achieved by CSS measures only.

Objective of this article – let us cover range II

We take these challenges step by step. For this article our goal is to cover Range II correctly – we do not care about Range I, yet. Already this limited objective will require some fundamental changes of our initial HTML code for Range III – and some interesting CSS tricks. But it will result in a solution which can also be used for Range I later on.

The order of the DIV tags prevents the repositioning of the DIV containers

We shall of course try to add some width dependent statements to our CSS file:

@media only screen and (min-width: 540px) and (max-width: 800px) { … }

But, what CSS directives could help us to move the container “div#right” to the bottom, i.e. below “div#main_cont” – without loosing our design achievements described in the last article? The frustrating result of many trials and errors was:

Given our assumptions above: none, actually. At least I was too stupid to find a reasonable solution.

You may play around a bit yourself. Even if you introduced some additional (floated) helper DIVs which would only be displayed in range II: Whatever you do to move the “div#right” – some of our requirements would be broken and/or the flow of the text in “div#main_cont” would be affected.

The first ugly thing is the following:
If we floated all major elements of the “div#float_cont” container “left” or “right” the final visible vertical order would not be correct. “div#main_cont” comes at the bottom of the original HTML code – so it moves to the bottom – whatever else happens. The same thing would happen if you turned off floating completely and used only relative positioning. You do not get the final order right.

So, what if we moved “div#main_cont” in our original HTML code and floated it instead of working with margins? Well, you cannot just float “div#main_cont” after (!) “div#left_nav” and keep up the adaptivity to any further width reduction of the viewport. This is mainly due to the fact that our left column container shall keep up a fixed width! You could solve
the adaptivity by changing “div#main_cont” to “position:absolute;” (and use “left:16rem; right:0;”) but then again you would loose any height control for “div#float_cont” and its external DIV containers. A loss of height control would also happen if you changed “div#info” to “position:absolute;” or if you floated it.

Now, you may think about additional floated helper DIVs which you switched on in the background of “div#main_cont” only after you passed a width threshold. But such a background floated DIV would have an impact onto any text written in “div#main_cont” ! Now, you could solve this by moving the text to an absolutely positioned inner container – and again loose height control. And so on, and so on …

What do we need?

From all my own experiments I could conclude 2 things which one needs to meet the adaptivity scenarios posed above:

  1. You need the HTML tag of “div#main_cont” as the first one inside the “div#float_cont” tag – more precise: before the other floated DIVs, which define the other columns.
  2. You sooner or later need “div#main_cont” to float itself.

However, as we have already seen at the end of our last article: Just moving the “div#main_cont” tag to the top and keeping up “position:relative;” will not work. However, if we floated it we would loose width adaptivity and/or would not get “div#left_cont” at the correct position with a fixed width. Is the last statement really true?

Negative margins to the rescue

We need to dig a bit deeper in our box of dirty CSS tricks. Let us assume that “div#main_cont” really were the first element of “div#float_cont”:

<div id="outer_cont">
	<div id="nav_left_bg"></div>
	<div id="right_bg"></div>
	<div id="info_bg"></div>
	<div id="float_cont">
		<div id="main_cont">
			<div id="info"> ... </div>
		</div>
		<div id="left_nav">
			<div id="left_inner"> ... </div>
		</div> 
		<div id="right">
			<div id="right_inner"> ... </div>
		</div> 
		<p class="floatstop"> </p>
	</div>
</div>

And let us further assume that we really floated “div#main_cont”. How could we regain width adaptivity and our text of “div#info” at the right position? Actually, this is pretty easy:

div#main_cont {
	position: relative; 	
	float: left;
	margin: 0; 
	width:100%;  /*adaptivity to the port view width!*/
}
div#info {
	/* height control: the contents defines the height of all parents */ 
	position: relative; 
	margin: 0 27% 0 16rem;
	padding: 0 0.8rem 0 0.8rem;
}

Note the “width:100%;” definition! It guarantees width adaptivity despite floating! To reserve enough pace at the left and right side of the main contents, we just use the same margin trick for “div#info” inside its container which we previously used for “div#main_cont” itself. Nice!

But, how do we get “div#left_nav” to its left position, now? We could use “position:absolute;” for this element. But, by doing this, we could no longer control container heights in case that the left column due to more contents ever became the longest of all columns. However, if we floated “div#left_nav”, it would move it downwards because “div#main_cont” already consumes all of the view port’s width.

Wait a second: What if we used negative margins? This could lead to a solution, because successive float elements can move across and over each other.
So, lets try it:

div#left_nav { 
	position:relative; 
	float:left;
	width: 14.2rem;
	margin-left: -100%;
}
div#right { 
	position:relative; 
	float:left;
	margin-left: -26%;
	width: 26%;
}

Some experiments with your browser would prove that this really works! After corresponding changes to the HTML and CSS codes we find that we more or less have come back to our original fluid scenario – but now the initial order of the HTML tags has changed fundamentally. Everything is floated – but by applying some tricky margins to our central containers we achieved the requested fluid layout again. How can we make use of the new tag order and the floating in range II ?

Elementary settings for range II

As soon as we reach the view port width threshold for range II, only some minor CSS changes are required for our new basic HTML/CSS layout:

@media only screen and (min-width: 540px) and (max-width: 800px) {
	div#right {
        	margin-left: 0;
		margin-top:1.0rem;
		width:100%;
		background-color: #FEEEBB;
	}
	div#info { 
		margin: 0 0 0 16rem;
	}
	div#right_bg {
		visibility: hidden; 
	}
	div#info_bg {
		right:0;
	}
}

This is pretty cool, isn’t it? We use our 100%-width of “div#main_cont”, change only one “left-margin” statement – and we have moved the “div#right” down. In addition we change the “right-margin” of “div#info” to use the total (adaptive!) width to the right for our main text information. The rest of the CSS changes just repairs some small gaps in the “optical appearance”:

fluid4

How to improve the optical appearance in Range III and Range II substantially?

As a developer, I thought I had achieved something and deserved a beer. However, I met strong criticism from my wife, who cares much more for optical design aspects than me. She pretty soon detected a major drawback of my HTML/CSS design:

The repositioned “div_right” still extends the height of the container “div#float_cont”. Thus, the heights of our colored background stripes “nav_left_bg”, “info_bg” and “right_bg” get bigger, too. The horizontally enlarged and moved “div_right” just overlaps and hides the background stripes.

Note, that we defined a “margin-top:10px;” for the repositioned “div#right;” in the CSS code above. Nevertheless, you see that our background stripes stretch down without any visible gap. As a result, I was confronted with a new requirement:

New additional requirement

What if you wanted to decouple the repositioned “div#right” from the 2 floated DIV containers above throughout Range II – in a clear optical way by a vertical gap – and also in the sense of HTML? And provide a region or stripe of transparency in between – i.e. in the gap opened by the margin-top definition)? Transparency, to allow the user e.g. to look at a background image of the whole page?

This requirement gave me some headache. But I understood the related objective very well. Web pages with seemingly floating areas (with rounded corners) above background images do look fancy, true enough. The eventual solution required additional changes to our initial HTML setup. I had to implement a kind of repetition of our new basic approach in a more convoluted way. Decoupling inevitably leads to more
containers …

Before looking at the code, let us first have a look at the result. As a starting point the new page appearance within Range III:

fluid5

A comparison with the next picture provides an impression of “width fluidity” in Range III:

fluid6

Now, let us have a look at the transition to Range II:

fluid7

And the width fluidity again:

fluid8

Even my wife accepted this – though, of course, she did not like the chosen background colors and the spacing of my test example. But related changes are within the reach of her work domain, now.

The new HTML code

Here are the resulting HTML and CSS codes for our test example. In comparison to our first article the statements may look a bit more complex. But, we only apply already discussed recipes at additional nested tag levels:

New HTML code:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fluid standard 2</title>
<link href="css/fluid_standard2.css" rel="stylesheet">
</head>

<body>
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="hmen_knapp_cont" class="hmen_knapp_cont">
			<a id="hmen_knapp"></a>
		</div>
		<div id="hmen_cont" class="hmen_cont">
			<ul>
				<li><div class="hmen_pkt">h-menu 1</div></li>
				<li><div class="hmen_pkt">h-menu 2</div></li>
				<li><div class="hmen_pkt">h-menu 3</div></li>
				<li><div class="hmen_pkt">h-menu 4</div></li>
				<li><div class="hmen_pkt">h-menu 5</div></li>
				<li><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
				<li class="floatstop"></li>
			</ul>
		</div>
	</div>
	
	<div id="outer_cont">
		<div id="nav_left_bg"></div>
		<div id="right_bg">
			<!-- do not remove ! -->
			<div id="right_inner_bg"></div>
		</div>
		<div id="info_bg"></div>
		
		<div id="float_cont">
			<div id="main_cont">
				<div id="bg_left"></div>
				<div id="bg_info"></div>
				
				<div id="info_cont">
					<div id="float_info">
						<div id="info"> 
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>Lorem ipsum dolor .... </p>
							<p> </p>
							<p>This is the end, my friend<br> </
p>
						</div>
					</div>
					<div id="left_nav">
						<div id="left_inner">
							<p><a href="#">sub menu point 1</a></p>
							<p><a href="#">sub menu point 2</a></p>
							<p><a href="#">sub menu point 3</a></p>
						</div>
					</div>
					<p class="floatstop"> </p>
				</div>
			</div>
			
			<div id="right">
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>
					<p> </p>
					<p>Some stupid text - just to show something</p>
					<p>Some stupid text - just to show something</p>
				</div>
			</div> 
			
			<p class="floatstop"> </p>

		</div>
	</div>
</div>
</body>
</html>

 

You see that I took the decoupling of the left navigation area and the main contents container from the “div#right” container seriously. Actually, we reproduced the very same solution found above – but now inside an additional container “div#info_cont”, which itself resides inside the now floated container “div#main_cont”. Note, that by applying “position: relative;” for the container “div#info_cont” we can keep up full height control. Of course, in addition the floating inside the new additional float container “div#info_cont” has to be stopped to guarantee the dynamical propagation of height information to the outer enclosing DIVs.

As you can see, we placed some new and additional (colored) background DIVs inside the (now floated) “div#main_cont”. But throughout Range III we keep these elements hidden! However, in Range II we hide the original background stripes and activate the new ones instead.

The positioning of the container “div#right” works exactly as before – as log as we arrange for proper margins of “div#info”. To get a really nice padding of the background stripes in both ranges and to keep a constant distance of the contents of “div#right” to the contents of “div#info”, we need to include some more internal DIV containers – “div#right_inner_bg”, “div#left_inner”, “div#right_inner”. They provide some required “optical cosmetics”.

The new CSS code

The required new CSS statements for our example are the following:

   
@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
    margin-left:0; 
    margin-right:0;
    margin-top: 0; 
    background-image: url(../image/hg_drm_ap_7_1.jpg);
    background-repeat:no-repeat; 
    background-position:top center;
}

p { 
	font-size: 1.6rem; 
	line-height: 1.4;
	margin: 0;
}
	

div#all { 
	position:relative; 
	width:100%; 
	padding-bottom: 1.0rem;
	padding-top: 1.0rem;

}

/* The header region */	

div#head { 
	position:relative; 
	width:100%; 
	min-height: 3.0rem;
}

/* The main contents container */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	min-height: 10.0rem;
	margin-top:1.0rem;
}
	

/* some elementary width definitions */

div#left_nav,
div#nav_left_bg,
div#bg_left {
    width: 14rem;
    margin-left: 1.0rem;
}

div#right,
div#right_bg {
    width: 27%;
}


/* background elements for all columns in range I */

div#nav_left_bg, 
div#right_bg,
div#info_bg,
div#bg_left,
div#bg_info,
div#right_inner_bg {
    position: absolute;
    top:0;
    bottom:0;
    border-radius: 0.8rem;
}

div#nav_left_bg {
    position: absolute;
    left:0;
    
background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#info_bg {
    position: absolute;
    left:15.8rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}

div#right_bg {
    right: 0;
    border: 0px solid #F00;
    z-index:1;

}

div#right_inner_bg {
    left: 1.0rem;
    right: 1.0rem;
    background-color: #FEEEBB;
}


	
/* The float container and its basic elements */	

div#float_cont { 
    position:relative; 
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:5;
}

/* floated left main container and 
 * its background elements for range II*/

div#main_cont { 
    position: relative;
    float: left; 
    width:100%;
    min-height: 2.0rem;
    border: 0px solid #009900;
    z-index:2;
}


div#bg_left {
    visibility: hidden; 
    position: absolute;
    left:0;
    background-color: #DDD;
    border: 0px solid #F00;
    z-index:1;

}

div#bg_info {
    visibility: hidden; 
    left:16.0rem;
    right:27%; 
    background-color: #FFB;
    border: 0px solid #00F;
    z-index:1;
}


/* The main column */

div#info_cont {
    position: relative; 
    width:100%; 
    border:0px #F00 solid;
}

div#float_info { 
    position:relative; 
    float:left;
    width: 100%; 
    border: 0px solid #FF0000;
    z-index:2;
}

div#info { 
    position: relative; 
    margin: 0 27% 0 16rem;
    width:auto;
    padding:0.8rem;
    min-height:2.0rem;
    z-index: 1;
}


/* right column */

div#right { 
    position:relative; 
    float:left;
    margin-left: -27%;
    min-height:2.0rem;
    border: 0px solid #009900;
    z-index:2;
}

div#right_inner {
    position:relative;
    width:auto;
    margin-left: 1.0rem;
    margin-right: 1.0rem;
    padding: 0.8rem;
}


/* left column */

div#left_nav { 
    position:relative; 
    float:left;
    border: 0px solid #009900;
    margin-left: -100%;
    padding-left: 1.0rem;
    z-index:5;
}

div#left_inner {
    width:auto;
    padding: 0.8rem;
}


/* Support elements */ 	

p.floatstop {
	clear:both;
	height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}



/* contents of the upper horizontal menu */

div.hmen_cont {
	display: block; 
	position: relative;
	min-height: 3.0rem;
	width: auto;
	margin-right:0.8rem;
	margin-left:0.8rem;
	background-color: #ccc;
	border-radius: 1.0rem;
}

div.hmen_cont ul {
	position: relative;
	list-style-type: none;
	width: 100%;
	margin: 0;
	padding: 0;
}

div.hmen_cont ul li {
	float: left;
	padding: 0.2rem 4.0rem 0.2rem 4.0rem;
	border-right: #a90000 0.2rem solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}
	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	min-height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

div#hmen_knapp_cont {
	display: none;
	position: absolute;
	right:0;
	top:10px;
	width: 50%;
	height: 2.4rem;
	border: 1px #A90000 solid;
}

a#hmen_knapp {
	display: block;
	width: 100%;
	height: 100%;
	background-color: #009999;		
}

a#but2 {
	display: block;
	width: 2.0rem;
	height: 2.0rem;
	background-color: #EEE;
}


/* @media screen decision and settings for range II */

@media screen and (min-width : 540px) and (max-width :800px) {
    
    div#info { 
        margin: 0 1.0rem 0 16rem;
        background-color:transparent;
    
    }
   
    div#right { 
        position:relative; 
        float:left;
        margin-top:1.0rem;
        margin-left: 0;
        margin-
right: 0; 
        width:100%;
     }
    
    div#right_inner {
        margin-left: 1.0rem; 
        margin-right: 1.0rem;
        width: auto; 
        background-color: #FEEEBB;
        border-radius: 0.8rem;
    }
    
    
    div#right_bg {
        visibility: hidden; 
    }

    div#info_bg {
        right:0;
    }

    div#nav_left_bg {
        visibility: hidden; 
    }

    div#info_bg {
        visibility: hidden; 
    }
    
    div#bg_left { 
        visibility: visible;
    } 
  
    div#bg_info { 
        visibility: visible;
        right: 1.0rem;
    }
}

 
Of course, you should replace the background image by your own.

Whilst studying the CSS statements, you may find that we extended the width of the right column to 27% of the view port width (instead of 26% before). Thus, physically “div#right” now connects directly to “div#info” at its left side. We did this to build up a seemingly constant (and not percentage dependent) visual distance between the middle and the right container contents. This may sound contradictory at first – but by using the additional inner DIV containers mentioned above and by applying proper margin/padding definitions we do achieve this objective.

At the bottom of the CSS file you find the necessary “@media screen” decisions and settings for range II. Because we set the negative left margin for “div#right” to zero this container moves down. The display of its contents requires some cosmetics; but the related CSS statements are very elementary.

One of the more interesting tricks is that we obviously hide the original (absolutely positioned and colored) background stripe containers and activate the new inner colored background stripe containers instead as soon as we perform the transition to Range II. This allows for a transparent gap between the visible upper part and the lower part of the web page in Range II – without loosing any height control for the visual columns in the upper part.

What have we achieved for our view port Ranges III and II?

Actually, all we wanted. We keep up optical height control where necessary in Range III and II. The CSS height of each of the container DIVs which represent the columns is completely determined by its contents (which we could change at any time, e.g. by a CMS). The longest of the three columns stirs the apparent visual height of all three columns in Range III.

In Range II we detach “div#right” and move it to the bottom of the page. Although we now create a gap between “div#right” and the upper part of the web page, full height control remains in place both for the floating 2 upper DIVs containers and their enclosing containers.

Fluidity is completely fulfilled in accordance with our objectives both for Range III and Range II.

The transition between Range III and Range II requires only some minor and obvious CSS changes. We did not have to change the HTML code at all during transition. We did not involve any Javascript so far.

Enough for today. We have not done anything, yet, to cover the objectives for Range I. I shall describe some appropriate measures in the next article:

Responsive fluid multi-column layouts – the DIV order problem – III

A good question the reader may consider in the meantime is:
What needs to be changed if we wanted to move the third column of Range III upwards instead of downwards during the transition to Range II?

Responsive fluid multi-column layouts – the DIV order problem – I

My wife works on a web project in Norway. As it is typical these days, functionality gets more weight than design when displaying web pages on mobile devices. Especially after Google’s decisions to explicitly require responsiveness. [Which was in my opinion mostly driven by their own interests to promote Android driven devices and their own development tools. And then maybe by general market and selling requirements. But this is a different story… ]

IT and communication in Norway is all about smart phones and tablets. So, of course, our Norwegian customer wanted to get some “responsive design”. OK … Although not really interested, I do take some challenges when my wife asks me for assistance …

As I am getting pretty IT-conservative with growing age my starting point with “responsiveness” was a fluid layout combined with “@media screen” CSS decisions – and a reluctance to use Javascript [JS] and jQuery. (This reluctance, of course, reflects the nagging question: What do we do when a customer has deactivated JS?)

My opinion was: In a certain range of browser window width – or more generally viewport width – the fluidity of the web design should predominantly govern the responsive reaction to a width change or a small target screen. I regard such a careful JS-free approach to responsive web design as reasonable – it helps already with different browser sizes on standard desktop screens – completely independent of mobile devices.

More extreme measures as removing visible parts of the layout or rearranging the positions, sizes and vertical order of whole HTML DIV container blocks would in this approach only be involved below certain viewport width thresholds via “@media” decisions. All in all, no big deal, I thought …. (In the first articles of this series we do not look at more detailed things as image resizing or font size adaptions).

What I underestimated is the fact that “old fashioned” fluid layouts may require a certain HTML tag order when we define its multiple columns”. However, in a responsive design approach you may eventually stumble across situations where the natural DIV element order you are used to is NOT suitable below some viewport width limits – i.e. when you want to rearrange the position and size of these DIV containers in a certain way. It took me some time to realize that there was a kind of contradiction between fluidity and responsiveness.

At this point I felt tempted again to use jQuery to freely rearrange the order of nodes of container DIVs in the HTML node tree or to use CSS flex boxes. However, flexboxes are not really standardized yet – and once you start with jQuery and responsiveness you must take care to align your Javascript conditions with the CSS @media decisions in a precise and non contradictory way. In addition I thought it better to understand JS free options and possibilities first before implementing some parametrizable JS machinery.

Whilst experimenting I all the time had the feeling that with some dirty CSS trick one should be able to gain control over the DIV element order – without destroying the optical appearance – and become able to adapt it to any predefined responsive reordering requirements for small viewport widths. I needed some hours to figure out how I could overcome at least some of the problems. I had some fun with this and maybe others find tweaking CSS interesting, too.

In this first article of a series I like to describe a standard flexible 3 column fluid layout and the CSS directives behind it. In a second article I shall add some elementary responsiveness and deduct the problem of the DIV order. Then I shall describe some CSS “trick” which will enable us to (manually) change or adapt the required DIV order in the HTML code without loosing fluidity and without loosing the optical appearance of the inital horizontal sequence of our columns. This will help us to achieve the pursued responsive and
vertical container rearrangement with a few very simple CSS changes below some width threshold of the viewport.

In other words: Our first objective is find CSS manipulations which enable us to code any required DIV order for the column grid in HTML such

  • that we optically do not change the column order or layout,
  • that we do not disturb the ability of our 3 column layout to adapt optically to the height of its largest (= highest) column,
  • that we can produce any predefined vertical content order of the column contents during a responsive reaction to view port width changes
  • and that we at the same time can realize responsiveness easily by basically changing some float or position statements in @media screen based decisions in our CSS file.

Note that the second point may impose additional difficulties!

Note further that even if we fulfilled our list of objectives we still would have to plan ahead our HTML code and its DIV order for the specific responsive layout we want to achieve. But being able to choose the required DIV order for our HTML code would already gives us the chance to create templates for different responsive reactions required by customers. In combination e.g. with PHP such templates would give us the freedom to switch any horizontal column order into any desired vertical order during responsive reactions – without involving JS.

Nevertheless, later in this series I want to show how we can use Javascript/jQuery to enforce a predefined responsive vertical order of the column contents from a basic 3-column layout we develop in the next two articles. But, at least I myself need some learning curve to get there.

We shall concentrate our efforts on a fluid 3-column layout.

Study example: A fluid/fixed layout with 3 columns

Below, we construct a simple example of a 3 column layout, where you arrange

  • a kind of navigation column on the left web page side,
  • a main contents area in the middle
  • and some column like area, e.g. for teaser elements, on the right page side.

Your fluidity objectives for a certain range of viewport widths would e.g. be

  • that the left column keeps up a fixed width,
  • whereas the middle main column changes its width adaptively with the viewport width
  • and that the right column adapts its width to a defined percentage of the viewport width.

Thus we have defined a combination of fixed/fluid elements which provides a good insight into the basic CSS techniques. Generalizations will be obvious. You require of course that the width adaption happens automatically whilst resizing the viewport width in the given range. All this should be governed by CSS statements, only. Imagine now, that you want to tweak things a bit further in the form that the longest of the three defined “columns” determines the height of the whole page and the apparent “visible” height of the other (not so high) columns, too. Above a responsiveness width threshold at least.

Typically, the main content column may become largest in size (height), whereas e.g. the left column may contain only some few menu points. Nevertheless, you want to create an appearance as if all 3 columns got the same height as the largest one with respect to height – and this should work automatically even if you changed the text and image contents of the content column significantly (e.g. via an CMS). This means that in case you add many lines of new text to the middle column the whole page and its right and left areas should somehow
optically adapt in height.
And: Everything should work analogously in case either the left or right column becomes the largest in height on some pages.

There is of course a simple, “conservative” CSS solution for this problem which I shall discuss a bit below. However, the requirement of fluidity in width in combination with the requirement of extending the web pages height automatically will unavoidably lead to a certain order of the HTML DIV containers.

Some details of our conventional fluid approach

The HTML file is pretty simple:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fluid standard</title>
<link href="css/fluid_standard.css" rel="stylesheet">
</head>

<body>
<!-- 3 spalten layout -->
<div id="all">
	<div id="head">
		<div id="hmen_knapp_cont" class="hmen_knapp_cont">
			<a id="hmen_knapp"></a>
		</div>
		<div id="hmen_cont" class="hmen_cont">
			<ul>
				<li><div class="hmen_pkt">h-menu 1</div></li>
				<li><div class="hmen_pkt">h-menu 2</div></li>
				<li><div class="hmen_pkt">h-menu 3</div></li>
				<li><div class="hmen_pkt">h-menu 4</div></li>
				<li><div class="hmen_pkt">h-menu 5</div></li>
				<li><div class="hmen_pkt"><a id="but2" href="#"></a></div></li>
				<li class="floatstop"></li>
			</ul>
		</div>
	</div>
	
	<div id="outer_cont">
		<div id="nav_left_bg"></div>
		<div id="right_bg"></div>
		<div id="info_bg"></div>
		
		<div id="float_cont">
			<div id="left_nav">
				<p><a href="#">sub menu point 1</a></p>
				<p><a href="#">sub menu point 2</a></p>
				<p><a href="#">sub menu point 3</a></p>
			</div> 
			<div id="right">
				<div id="right_inner">
					<p>Some stupid text - just to show something</p>
					<p> </p>
					<p>Some stupid text - just to show something</p>
				</div>
			</div> 
			<div id="main_cont">
				<div id="info"> 
					<p>Lorem ipsum .... </p>
					<p> </p>
					<p>Lorem ipsum ....</p>
					<p> </p>
					<p>This is the end, my friend<br> </p>
				</div>
			</div>
			<p class="floatstop"> </p>
		</div>
	</div>
</div>
</body>
</html>

The central DIV that shall contain most of the information of our web page is “div#info”. Why we have placed it inside an extra container “div#main_cont” may become clear in the next article. In the pure fluid approach it would not really have been necessary. You clearly detect the “left” sided DIV for e.g. a column with navigation points and the “right” sided DIV, which may contain something else. Note that there are “background” DIVs (with an ID like “…_bg”) that provide the impression of colored columns with exactly the length of the “outer_cont” DIV.

To understand the fluid behavior let us have a look at the CSS :

@CHARSET "UTF-8";

html {
	font-size:10px;
}

body {
    margin-left:0; 
    margin-right:0;
}

p { 
	font-size: 1.6rem; 
	line-height: 1.4;
	margin: 0;

}

div#all { 
	position:relative; 
	width:100%; 
	background-color: #000; 
}

/* The header region */	

div#head { 
	position:relative; 
	width:100%; 
	background-color: #FF0; 
	min-height: 3.0rem;
}

/* The main contents container */
	
div#outer_cont { 
	position:relative; 
	width:100%; 
	background-color: #FF0; 
	min-height: 10.0rem;
}
	
/* background elements for all columns */

div#left_nav,
div#nav_left_bg {
    width: 14.2rem;
}

div#right,
div#right_bg {
    width: 26%;
}

div#nav_left_bg, 
div#right_bg,
div#info_bg {
    top:0;
    bottom:0;
}

div#nav_left_bg {
    position: absolute;
    left:0;
    background-color: #DDD;
    z-index:1;
    border: 1px solid #F00;
}

div#right_bg {
    position: absolute;
    right: 0;
    background-color: #FEEEBB;
    z-index:1;
    border: 0px solid #F00;
}

	
/* The float container and its basic elements */	

div#float_cont { 
	position:relative; 
	width: 100%; 
	/*background-color: #99e;*/ 
	border: 0px solid #FF0000;
	z-index:5;
}


/* left column */

div#left_nav { 
    position:relative; 
    float:left;
    border: 0px solid #009900;
    padding-left:8px;
    /*margin-left: -320px;*/
    z-index:2;
}

/* middle column */

div#main_cont { 
	position: relative;
	margin: 0 27% 0 15rem;
	/*width:74%;*/
	/*min-width: 462px;*/
	/*background-color: #fff;*/ 
	border: 0px solid #009900;
	z-index:2;
}

div#info { 
	position: relative; 
	/*margin: 0 2px 0 160px;*/  
	width:auto;
	background-color: #0f0; 
	padding-left:8px;
	/*height:200px;*/
}

/* right column */

div#right { 
	position:relative; 
	float:right;
	/*min-width: 15.2rem;*/ 
	width: 26%;
	/*background-color: #00f;*/ 
	border: 0px solid #009900;
}

div#right_inner {
    width:auto;
    padding: 0 0.8rem 0 0.8rem;
}


/* Other elements */ 	

p.floatstop {
	clear:both;
	height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

/* contents of the upper horozontal menu */
div.hmen_cont {
	display: block; 
	position: relative;
	min-height: 3.0rem;
	width: 100%;
	background-color: #ccc;
}

div.hmen_cont ul {
	position: relative;
	list-style-type: none;
	width: 100%;
	margin: 0;
	padding: 0;
	background-color: #00EE00;
}

div.hmen_cont ul li {
	float: left;
	padding: 2px 40px 2px 40px;
	border-left: #ffffff 1px solid;
	border-right: #a90000 1px solid;
	min-height: 2.0rem;
	font-size: 1.6rem;
}
	
div.hmen_cont ul li.floatstop {
	float:none;
	clear:both;
	min-height:0;
	margin:0;
	line-height:0;
	padding: 0;
	font-size: 0;
}

div#hmen_knapp_cont {
	display: none;
	position: absolute;
	right:0;
	top:10px;
	width: 50%;
	height: 2.4rem;
	border: 1px #A90000 solid;
}

a#hmen_knapp {
	display: block;
	width: 100%;
	height: 100%;
	background-color: #009999;		
}

a#but2 {
	display: block;
	width:20px;
	height: 20px;
	background-color: #eee;
}

Note that all of the DIVs

  • div#all,
  • div#outer_cont,
  • div#float_cont,
  • div#main_cont,
  • div#info

got a “position: relative;”, but none got any defined height. Actually, as we stop the floating at the end of “div#float_cont” via an artificial p-element, the “div#float_cont” container responds dynamically to the maximum height of one of its inner floated elements or relatively positioned elements and imposes its own height on the surrounding DIV container “div#outer_cont”. The resulting height of this container can be used to indirectly define the height of absolutely positioned background
DIV elements which “simulate” a colored virtual height of all columns – at least as long as we do not rearrange DIV containers due to responsiveness.

Whereas the width adaptivity of the right column is understandable from its CSS width statement, the adaptivity of the “div#main_cont” results from its relative positioning with left and right margins on top of the vertical level which is automatically defined by the floated elements inside the “div#float_cont”. The margins guarantee that we leave enough space on the left and right side not to cover the floated columns contents there.

This fluid design is quite adaptive already – see the following images taken with Firefox.

fluid1

and

fluid2

But, of course, the column adaptivity will not be of much use on small smart phone displays: First there comes an end to shrinking as soon as some words get too large to fit into their column width. And on a smart phone we would appreciate very much to use all of the available width for the text in the main column. We shall look at related measures in the next article.

For now, I want you to realize that the above fluid design relies totally on the order of the DIVs inside the “div#float_cont”. See, what happens, if we changed the order of the DIVs to

<div id="float_cont">
	<div id="main_cont"> ... </div>
	<div id="left_nav">..</div>
	<div id="right"> ... </div>
	<p class="floatstop"> </p>
</div>

fluid3

Then the first DIV spans a region with height that defines the position of the layer for the floated elements – namely below the “div#main_cont”. So our nice fluid design would be doomed to fail in case we needed some other DIV order.

Outlook on forthcoming posts

In the next post

Responsive fluid multi-column layouts – the DIV order problem – II

we introduce some elementary responsiveness and discuss, why the defined DIV order in the HTML code may conflict with a responsive rearrangement. We shall present a responsive solution where some few CSS tricks allow us to (manually) change the DIV order in the HTML code in whatever required way to maintain an easy realization of responsiveness. See also

Responsive fluid multi-column layouts – the DIV order problem – III

for a full discussion.

In the articles
Responsive fluid multi-column layouts – with a responsive horizontal menu – IV
and
Responsive fluid multi-column layouts – with a responsive horizontal menu – V
we discuss menu adaptions without and with Javascript.

Our final objective is to use JS to switch the DIV tag order in the HTML node tree for
a basic 3 column layout in accordance with the required DIV order for a defined responsiveness – in a parametrized way.